<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Albert World</title>
  <icon>https://www.gravatar.com/avatar/8ccc6291d5bfd34e7aaf601c4ebec7e1</icon>
  
  <link href="/blog/atom.xml" rel="self"/>
  
  <link href="http://AlbertGithubHome.github.io/blog/"/>
  <updated>2023-10-08T15:10:27.895Z</updated>
  <id>http://AlbertGithubHome.github.io/blog/</id>
  
  <author>
    <name>Albert Shi</name>
    <email>347070901@qq.com</email>
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>废了九牛二虎之力终于修改了MySQL8.0的root密码</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/10/08/%E5%BA%9F%E4%BA%86%E4%B9%9D%E7%89%9B%E4%BA%8C%E8%99%8E%E4%B9%8B%E5%8A%9B%E7%BB%88%E4%BA%8E%E4%BF%AE%E6%94%B9%E4%BA%86MySQL8-0%E7%9A%84root%E5%AF%86%E7%A0%81/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/10/08/废了九牛二虎之力终于修改了MySQL8-0的root密码/</id>
    <published>2023-10-08T12:56:28.000Z</published>
    <updated>2023-10-08T15:10:27.895Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>上次说到在Ubuntu系统上安装MySQL8.0之后默认是没有密码的，如果想设置密码需要写成<code>ALTER USER &#39;root&#39;@&#39;localhost&#39; IDENTIFIED WITH mysql_native_password BY &#39;xxx&#39;;</code> 的形式，其实在用这种方式之前，我还使用功能了mysql_secure_installation这个MySQL自带的程序来设置密码，但是没生效，为什么这个看起来很正规的方式不生效的呢？接下来一起找找原因。</p><a id="more"></a><h1 id="使用mysql-secure-installation"><a href="#使用mysql-secure-installation" class="headerlink" title="使用mysql_secure_installation"></a>使用mysql_secure_installation</h1><p>使用步骤比较简单，就按照提示来输入就可以了，这是我第二次运行，第一次运行时还要加个 ‘Would you like to setup VALIDATE PASSWORD component?’ 步骤</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line">root@w-dev:~<span class="comment"># mysql_secure_installation</span></span><br><span class="line"></span><br><span class="line">Securing the MySQL server deployment.</span><br><span class="line"></span><br><span class="line">Enter password <span class="keyword">for</span> user root:</span><br><span class="line">The <span class="string">'validate_password'</span> component is installed on the server.</span><br><span class="line">The subsequent steps will run with the existing configuration</span><br><span class="line">of the component.</span><br><span class="line">Using existing password <span class="keyword">for</span> root.</span><br><span class="line"></span><br><span class="line">Estimated strength of the password: 50</span><br><span class="line">Change the password <span class="keyword">for</span> root ? ((Press y|Y <span class="keyword">for</span> Yes, any other key <span class="keyword">for</span> No) : y</span><br><span class="line"></span><br><span class="line">New password:</span><br><span class="line"></span><br><span class="line">Re-enter new password:</span><br><span class="line"></span><br><span class="line">Estimated strength of the password: 50</span><br><span class="line">Do you wish to <span class="built_in">continue</span> with the password provided?(Press y|Y <span class="keyword">for</span> Yes, any other key <span class="keyword">for</span> No) : y</span><br><span class="line">By default, a MySQL installation has an anonymous user,</span><br><span class="line">allowing anyone to <span class="built_in">log</span> into MySQL without having to have</span><br><span class="line">a user account created <span class="keyword">for</span> them. This is intended only <span class="keyword">for</span></span><br><span class="line">testing, and to make the installation go a bit smoother.</span><br><span class="line">You should remove them before moving into a production</span><br><span class="line">environment.</span><br><span class="line"></span><br><span class="line">Remove anonymous users? (Press y|Y <span class="keyword">for</span> Yes, any other key <span class="keyword">for</span> No) : y</span><br><span class="line">Success.</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Normally, root should only be allowed to connect from</span><br><span class="line"><span class="string">'localhost'</span>. This ensures that someone cannot guess at</span><br><span class="line">the root password from the network.</span><br><span class="line"></span><br><span class="line">Disallow root login remotely? (Press y|Y <span class="keyword">for</span> Yes, any other key <span class="keyword">for</span> No) : y</span><br><span class="line">Success.</span><br><span class="line"></span><br><span class="line">By default, MySQL comes with a database named <span class="string">'test'</span> that</span><br><span class="line">anyone can access. This is also intended only <span class="keyword">for</span> testing,</span><br><span class="line">and should be removed before moving into a production</span><br><span class="line">environment.</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Remove <span class="built_in">test</span> database and access to it? (Press y|Y <span class="keyword">for</span> Yes, any other key <span class="keyword">for</span> No) : y</span><br><span class="line"> - Dropping <span class="built_in">test</span> database...</span><br><span class="line">Success.</span><br><span class="line"></span><br><span class="line"> - Removing privileges on <span class="built_in">test</span> database...</span><br><span class="line">Success.</span><br><span class="line"></span><br><span class="line">Reloading the privilege tables will ensure that all changes</span><br><span class="line">made so far will take effect immediately.</span><br><span class="line"></span><br><span class="line">Reload privilege tables now? (Press y|Y <span class="keyword">for</span> Yes, any other key <span class="keyword">for</span> No) : y</span><br><span class="line">Success.</span><br><span class="line"></span><br><span class="line">All <span class="keyword">done</span>!</span><br></pre></td></tr></table></figure><p>那么这个程序具体干了哪些事呢，下面来解释一下。</p><p>mysql_secure_installation 是一个 MySQL 提供的命令行工具，用于帮助加强和改善 MySQL 服务器的安全性。当首次安装 MySQL 后，运行此工具可以执行一系列的操作来提高 MySQL 服务器的安全性，并确保默认设置不会导致潜在的安全漏洞。以下是 mysql_secure_installation 工具执行的主要任务：</p><p><strong>设置 root 密码：</strong> 如果您在安装 MySQL 时没有为 root 用户设置密码，mysql_secure_installation 会提示您设置 root 密码。设置 root 密码是非常重要的，因为 root 用户具有最高的权限，拥有对 MySQL 服务器的完全控制。</p><p><strong>删除匿名用户：</strong> mysql_secure_installation 会删除默认情况下存在的匿名用户。匿名用户是指没有用户名和密码就能访问 MySQL 服务器的用户。删除这些用户可以减少潜在的安全风险。</p><p><strong>禁止远程 root 登录：</strong> 默认情况下，MySQL 允许 root 用户从任何主机登录。mysql_secure_installation 会提示您是否禁止 root 用户从远程主机登录，以减少潜在的远程攻击风险。</p><p><strong>删除测试数据库：</strong> 默认情况下，MySQL 安装包含一个名为 “test” 的测试数据库，这个数据库可能会成为潜在的攻击目标。mysql_secure_installation 会提示您是否删除测试数据库。</p><p><strong>刷新权限：</strong> 完成上述任务后，mysql_secure_installation 会刷新 MySQL 权限表，以确保更改生效。</p><p>mysql_secure_installation 工具有助于在 MySQL 安装完成后采取一些基本的安全措施，以减少潜在的风险和攻击。然而，这只是开始，为了更好地保护 MySQL 服务器和数据，您还应该采取其他安全措施，如定期备份、限制用户权限、监控数据库活动等。</p><h1 id="mysql-secure-installation-为什么设置root密码无效"><a href="#mysql-secure-installation-为什么设置root密码无效" class="headerlink" title="mysql_secure_installation 为什么设置root密码无效"></a>mysql_secure_installation 为什么设置root密码无效</h1><p>看上面的描述这个程序挺靠谱的，为啥设置密码不好使呢？遵循着网络大神们的脚本，我找到了一个 <a href="https://github.com/twitter-forks/mysql/blob/master/scripts/mysql_secure_installation.sh" target="_blank" rel="noopener">mysql_secure_installation.sh</a>脚本，来自[twitter-forks/mysql]开源库，其中包含这样一段代码：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">esc_pass=`basic_single_escape "$password1"`</span><br><span class="line">do_query "UPDATE mysql.user SET Password=PASSWORD('$esc_pass') WHERE User='root';"</span><br><span class="line">if [ $? -eq 0 ]; then</span><br><span class="line">echo "Password updated successfully!"</span><br><span class="line">echo "Reloading privilege tables.."</span><br><span class="line">reload_privilege_tables</span><br><span class="line">if [ $? -eq 1 ]; then</span><br><span class="line">    clean_and_exit</span><br><span class="line">fi</span><br><span class="line">echo</span><br><span class="line">rootpass=$password1</span><br><span class="line">make_config</span><br><span class="line">else</span><br><span class="line">echo "Password update failed!"</span><br><span class="line">clean_and_exit</span><br><span class="line">fi</span><br></pre></td></tr></table></figure><p><code>&quot;UPDATE mysql.user SET Password=PASSWORD(&#39;$esc_pass&#39;) WHERE User=&#39;root&#39;;&quot;</code> 这个脚本里居然用了我们之前提到的已经失效的更新方式，但是我发现我在Ubuntu系统下安装的mysql_secure_installation是一个二进制程序，不是shell脚本，所以我又找到了<a href="https://github.com/mysql/mysql-server/blob/8.0/client/mysql_secure_installation.cc#L425" target="_blank" rel="noopener">mysql的源码</a>程序，其中包含这样一段：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">  query =</span><br><span class="line">      (<span class="keyword">char</span> *)my_malloc(PSI_NOT_INSTRUMENTED, password_len + <span class="number">50</span>, MYF(MY_WME));</span><br><span class="line">  end = my_stpmov(query, <span class="string">"SET PASSWORD="</span>);</span><br><span class="line">  *end++ = <span class="string">'\''</span>;</span><br><span class="line">  end += mysql_real_escape_string_quote(mysql, end, password,</span><br><span class="line">                                        (ulong)password_len, <span class="string">'\''</span>);</span><br><span class="line">  *end++ = <span class="string">'\''</span>;</span><br><span class="line">  <span class="keyword">if</span> (mysql_real_query(mysql, query, (ulong)(end - query))) &#123;</span><br><span class="line">    my_free(query);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>里面包含了<code>&quot;SET PASSWORD=&quot;</code>的字样，看起来不太靠谱的样子，需要注意的是，在 MySQL 5.7 版本之后，<code>SET PASSWORD</code> 命令已被弃用，推荐使用 <code>ALTER USER</code> 命令来修改密码。例如：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ALTER</span> <span class="keyword">USER</span> <span class="string">'root'</span>@<span class="string">'localhost'</span> <span class="keyword">IDENTIFIED</span> <span class="keyword">BY</span> <span class="string">'新密码'</span>;</span><br></pre></td></tr></table></figure><p>这个命令更加直观，并且与最新版本的 MySQL 兼容。同样，将 <code>&#39;新密码&#39;</code> 替换为您希望设置的新密码。</p><p>难道是MySQL 8.0没有更新工具的代码？暂时不得而知，这或许就是mysql_secure_installation设置密码不生效的原因。</p><h1 id="ALTER-USER再生事端"><a href="#ALTER-USER再生事端" class="headerlink" title="ALTER USER再生事端"></a>ALTER USER再生事端</h1><p>前文提到了一种更加直观的设置新密码的方式 <code>ALTER USER &#39;root&#39;@&#39;localhost&#39; IDENTIFIED BY &#39;新密码&#39;;</code>，但是在我这行不通，不仅我这行不通，网上也有很多人说设置不了，需要加上插件名称 <code>WITH mysql_native_password</code>，改为 <code>ALTER USER &#39;root&#39;@&#39;localhost&#39; IDENTIFIED WITH mysql_native_password BY &#39;新密码&#39;;</code> 才可以，这又是为什么呢？</p><p>执行 <code>select host,user,plugin,authentication_string from mysql.user;</code> 语句查看发现：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; select host,user,plugin,authentication_string from mysql.user;</span><br><span class="line">+<span class="comment">-----------+------------------+-----------------------+------------------------------------------------------------------------+</span></span><br><span class="line">| host      | user             | plugin                | authentication_string                                                  |</span><br><span class="line">+<span class="comment">-----------+------------------+-----------------------+------------------------------------------------------------------------+</span></span><br><span class="line">| localhost | zone1            | mysql_native_password | *4C7684A2CD28A1C3C55CC832E6O817FCE3C5B5DB                              |</span><br><span class="line">| localhost | mysql.infoschema | caching_sha2_password | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |</span><br><span class="line">| localhost | mysql.session    | caching_sha2_password | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |</span><br><span class="line">| localhost | mysql.sys        | caching_sha2_password | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |</span><br><span class="line">| localhost | root             | auth_socket           |                                                                        |</span><br><span class="line">| localhost | admin            | mysql_native_password | *F52COBE5FFB5FFCCD5BFD976OADEB81081AE4787                              |</span><br><span class="line">+<span class="comment">-----------+------------------+-----------------------+------------------------------------------------------------------------+</span></span><br></pre></td></tr></table></figure><p>原来每个账户还有默认的不同的密码管理插件，用于验证和管理用户密码，mysql_native_password、auth_socket和caching_sha2_password这三种插件的含义如下：</p><h2 id="mysql-native-password"><a href="#mysql-native-password" class="headerlink" title="mysql_native_password"></a>mysql_native_password</h2><ul><li>加密算法： 使用较旧的哈希算法，如SHA-1或SHA-256，取决于 MySQL 版本。</li><li>认证方式： 用户密码以哈希形式存储在系统表中，服务器在用户登录时将明文密码哈希并与数据库中存储的哈希进行比较。</li><li>支持远程登录： 支持远程密码登录，可以从远程主机登录 MySQL。</li><li>适用场景： 适用于需要与旧版 MySQL 兼容或需要与其他应用程序或系统进行集成的情况。</li></ul><h2 id="caching-sha2-password"><a href="#caching-sha2-password" class="headerlink" title="caching_sha2_password"></a>caching_sha2_password</h2><ul><li>加密算法： 使用 SHA-256 进行加密，提供更高级别的安全性。</li><li>支持版本： 从 MySQL 5.7.4 开始，caching_sha2_password 成为默认的密码插件。</li><li>认证方式： 用户密码以加密形式存储在系统表中，并且在用户登录时，服务器将明文密码哈希并与数据库中存储的哈希进行比较。</li><li>支持远程登录： 支持远程密码登录，可以从远程主机登录 MySQL。</li></ul><h2 id="auth-socket"><a href="#auth-socket" class="headerlink" title="auth_socket"></a>auth_socket</h2><ul><li>认证方式： 不存储密码哈希，而是依赖于操作系统（Linux）的用户认证机制。只有与操作系统用户账户关联的 MySQL 用户可以登录，而不需要密码。</li><li>适用场景： 通常用于本地连接，因为它要求数据库用户和操作系统用户名称一致。</li><li>安全性： 提供了一定的安全性，因为不需要存储密码哈希，但需要确保服务器的用户账户和 MySQL 用户账户的一致性。</li></ul><p>总结来说，这些密码插件之间的区别主要涉及到加密算法、认证方式以及适用场景。caching_sha2_password 提供更高级别的安全性，而 auth_socket 依赖于操作系统用户认证机制，mysql_native_password 则提供与旧版 MySQL 兼容性。使用前应根据安全性需求、用户管理和应用程序集成的要求来选择合适的密码插件。一般来说，推荐使用较新且安全性更高的密码插件，如caching_sha2_password。</p><p>测试后发现，MySQL8.0默认安装后root用户的默认插件是 <code>auth_socket</code>，这种插件没办法设置密码，所以指定插件为 <code>mysql_native_password</code> 时就可以设置密码了，但是根据建议，我们应该选择 <code>caching_sha2_password</code>插件。</p><h1 id="肯定好使的MySQL初始化方法"><a href="#肯定好使的MySQL初始化方法" class="headerlink" title="肯定好使的MySQL初始化方法"></a>肯定好使的MySQL初始化方法</h1><ul><li><code>apt install mysql-server</code></li><li><code>mysql</code></li><li><code>ALTER USER &#39;root&#39;@&#39;localhost&#39; IDENTIFIED WITH caching_sha2_password BY &#39;新密码&#39;;</code></li><li><code>flush privileges;</code></li><li><code>exit;</code></li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>安装MySQL8.0后使用<code>ALTER USER &#39;root&#39;@&#39;localhost&#39; IDENTIFIED WITH caching_sha2_password BY &#39;xxx&#39;;</code>设置密码</li><li>mysql_secure_installation工具不好使的原因目前来看是源代码没有工具的源代码没有及时更新，与MySQL版本不匹配</li><li>mysql_secure_installation另外的几项功能，比如删除匿名用户、删除测试数据库、禁止远程登录在安装完貌似都处理好了</li><li>综上来看，难道mysql_secure_installation工具已经被废弃了？</li></ul><hr><center><a href="https://blog.csdn.net/albertsh/article/details/132701267" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>你们的一切都是我的，只要留心你将获得无穷的助力，很多东西不必从头开始，既然做不成巨人，就从巨人的肩膀上出发吧~</p><p>证件照尺寸查询<br>一寸    295<em>413px   | 25</em>35mm<br>二寸    413<em>579px   | 35</em>49mm<br>小一寸  260<em>378px   | 22</em>32mm<br>大一寸  390<em>567px   | 33</em>48mm<br>小二寸  413<em>531px   | 35</em>45mm<br>大二寸  413<em>626px   | 35</em>53mm<br>三寸    650<em>992px   | 55</em>84mm<br>四寸    898<em>1181px  | 76</em>100mm<br>五寸    1050<em>1500px | 89</em>127mm</p><p>2023-10-8 20:56:28</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;上次说到在Ubuntu系统上安装MySQL8.0之后默认是没有密码的，如果想设置密码需要写成&lt;code&gt;ALTER USER &amp;#39;root&amp;#39;@&amp;#39;localhost&amp;#39; IDENTIFIED WITH mysql_native_password BY &amp;#39;xxx&amp;#39;;&lt;/code&gt; 的形式，其实在用这种方式之前，我还使用功能了mysql_secure_installation这个MySQL自带的程序来设置密码，但是没生效，为什么这个看起来很正规的方式不生效的呢？接下来一起找找原因。&lt;/p&gt;
    
    </summary>
    
      <category term="Mysql" scheme="http://AlbertGithubHome.github.io/blog/categories/Mysql/"/>
    
    
      <category term="Mysql" scheme="http://AlbertGithubHome.github.io/blog/tags/Mysql/"/>
    
      <category term="root" scheme="http://AlbertGithubHome.github.io/blog/tags/root/"/>
    
      <category term="mysql_secure_installation" scheme="http://AlbertGithubHome.github.io/blog/tags/mysql-secure-installation/"/>
    
      <category term="AlterUser" scheme="http://AlbertGithubHome.github.io/blog/tags/AlterUser/"/>
    
      <category term="修改密码" scheme="http://AlbertGithubHome.github.io/blog/tags/%E4%BF%AE%E6%94%B9%E5%AF%86%E7%A0%81/"/>
    
  </entry>
  
  <entry>
    <title>C++20中的关键字</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/10/07/C-20%E4%B8%AD%E7%9A%84%E5%85%B3%E9%94%AE%E5%AD%97/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/10/07/C-20中的关键字/</id>
    <published>2023-10-07T15:34:23.000Z</published>
    <updated>2023-10-08T15:08:12.248Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>源于上一篇中提到了 <code>decltype</code>是一个关键字，所以去查了现有的关键字列表，其实我对C/C++的关键字还停留在上世纪大概30多个的时代，虽然知道C++加了 <code>auto</code>、<code>constexpr</code> 等关键字，但查询过后才发现原来他扩充到了将近100个，而C语言的关键字也扩到了六七十个，有点震惊了。</p><a id="more"></a><h1 id="关键字列表"><a href="#关键字列表" class="headerlink" title="关键字列表"></a>关键字列表</h1><p><img src="https://img-blog.csdnimg.cn/571f2e791dfa484c82436da29a1e5b61.png" alt="keywords"><br>这份列表来源于<a href="https://en.cppreference.com/w/cpp/keyword" target="_blank" rel="noopener">cppreference</a>，感兴趣的可以去看原文，下面对这些关键字进行一个简单的介绍，有些还是很陌生的。</p><h1 id="关键字含义"><a href="#关键字含义" class="headerlink" title="关键字含义"></a>关键字含义</h1><table><thead><tr><th style="text-align:left">关键字</th><th style="text-align:left">含义</th></tr></thead><tbody><tr><td style="text-align:left">alignas</td><td style="text-align:left">指定类型或对象的对齐要求</td></tr><tr><td style="text-align:left">alignof</td><td style="text-align:left">查询类型的对齐要求</td></tr><tr><td style="text-align:left">and</td><td style="text-align:left">用作 <code>&amp;&amp;</code> 的替代写法</td></tr><tr><td style="text-align:left">and_eq</td><td style="text-align:left">用作 <code>&amp;=</code> 的替代写法</td></tr><tr><td style="text-align:left">asm</td><td style="text-align:left">汇编（asm）声明给予在 C++ 程序中嵌入汇编语言源代码的能力</td></tr><tr><td style="text-align:left">atomic_cancel (TM TS)</td><td style="text-align:left">原子块，若抛出异常，则调用 std::abort，除非该异常是用于事务取消的异常之一，这种情况下事务被取消</td></tr><tr><td style="text-align:left">atomic_commit (TM TS)</td><td style="text-align:left">原子块，若抛出异常，则正常地提交事务</td></tr><tr><td style="text-align:left">atomic_noexcept (TM TS)</td><td style="text-align:left">原子块，若抛出异常，则调用 std::abort</td></tr><tr><td style="text-align:left">auto</td><td style="text-align:left">可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型（C++11）</td></tr><tr><td style="text-align:left">bitand</td><td style="text-align:left">用作 <code>&amp;</code> 的替代写法</td></tr><tr><td style="text-align:left">bitor</td><td style="text-align:left">用作  `\</td><td>`  的替代写法</td></tr><tr><td style="text-align:left">bool</td><td style="text-align:left">足以存放两个值 true 或 false 之一的类型</td></tr><tr><td style="text-align:left">break</td><td style="text-align:left">控制将被转移到紧跟其外围循环或 switch 的下一位置</td></tr><tr><td style="text-align:left">case</td><td style="text-align:left">根据条件的值，将控制流转移到若干语句之一</td></tr><tr><td style="text-align:left">catch</td><td style="text-align:left">和try联合使用，将一或多个异常处理块（catch 子句）与复合语句关联</td></tr><tr><td style="text-align:left">char</td><td style="text-align:left">能在目标系统上最有效地处理的字符表示的类型</td></tr><tr><td style="text-align:left">char8_t</td><td style="text-align:left">UTF-8 字符表示的类型，要求大到足以表示任何 UTF-8 编码单元（ 8 位）</td></tr><tr><td style="text-align:left">char16_t</td><td style="text-align:left">UTF-16 字符表示的类型，要求大到足以表示任何 UTF-16 编码单元（ 16 位）</td></tr><tr><td style="text-align:left">char32_t</td><td style="text-align:left">UTF-32 字符表示的类型，要求大到足以表示任何 UTF-32 编码单元（ 32 位）， C++ 标准还保证 1 == sizeof(char) &lt;= sizeof(short) &lt;= sizeof(int) &lt;= sizeof(long) &lt;= sizeof(long long)</td></tr><tr><td style="text-align:left">class</td><td style="text-align:left">声明类</td></tr><tr><td style="text-align:left">compl</td><td style="text-align:left">用作 <code>~</code> 的替代写法</td></tr><tr><td style="text-align:left">concept</td><td style="text-align:left">声明具名类型要求</td></tr><tr><td style="text-align:left">const</td><td style="text-align:left">类型限定符，定义类型为常量</td></tr><tr><td style="text-align:left">consteval</td><td style="text-align:left">指定函数是立即函数（immediate function），即每次调用该函数必须产生编译时常量</td></tr><tr><td style="text-align:left">constexpr</td><td style="text-align:left">指定变量或函数的值可在常量表达式中出现，在 constexpr if 语句中，条件的值必须是可按语境转换到 bool 类型的经转换常量表达式</td></tr><tr><td style="text-align:left">constinit</td><td style="text-align:left">断言变量拥有静态初始化，即零初始化与常量初始化</td></tr><tr><td style="text-align:left">const_cast</td><td style="text-align:left">用const_cast来去除const限定</td></tr><tr><td style="text-align:left">continue</td><td style="text-align:left">导致外围 for、范围 for、while 或 do-while 循环体的剩余部分被跳过</td></tr><tr><td style="text-align:left">co_await</td><td style="text-align:left">协程是能暂停执行以在之后恢复的函数</td></tr><tr><td style="text-align:left">co_return</td><td style="text-align:left">协程中的 return 语句</td></tr><tr><td style="text-align:left">co_yield</td><td style="text-align:left">协程中表达式向调用方返回一个值并暂停当前协程</td></tr><tr><td style="text-align:left">decltype</td><td style="text-align:left">检查实体的声明类型，或表达式的类型和值类别</td></tr><tr><td style="text-align:left">default</td><td style="text-align:left">1.switch 语句用于声明默认情况标号。2.显式默认化的函数定义令编译器为某个类生成特殊成员函数或比较运算符的显式指令</td></tr><tr><td style="text-align:left">delete</td><td style="text-align:left">1.销毁先前由 new 表达式分配的对象，并释放获得的内存区域。2.弃置函数(C++11 起)</td></tr><tr><td style="text-align:left">do</td><td style="text-align:left">do-while 循环，用作循环的声明</td></tr><tr><td style="text-align:left">double</td><td style="text-align:left">浮点类型</td></tr><tr><td style="text-align:left">dynamic_cast</td><td style="text-align:left">沿继承层级向上、向下及侧向，安全地转换到其他类的指针和引用</td></tr><tr><td style="text-align:left">else</td><td style="text-align:left">if 语句用作另一分支的声明</td></tr><tr><td style="text-align:left">enum</td><td style="text-align:left">声明枚举类型</td></tr><tr><td style="text-align:left">explicit</td><td style="text-align:left">1.指定构造函数或转换函数 (C++11 起)或推导指引 (C++17 起)为显式，即它不能用于隐式转换和复制初始化。2.explicit 说明符可以与常量表达式一同使用。当且仅当该常量表达式求值为 true 时函数为显式。</td></tr><tr><td style="text-align:left">export</td><td style="text-align:left">1.用于标记模板定义为被导出，这允许在其他翻译单元中声明但不定义同一模板。(C++11 前) 2.不使用并保留该关键词。(C++11 起)(C++20 前) 3.标记一个声明、一组声明或另一模块为当前模块所导出。(C++20 起)</td></tr><tr><td style="text-align:left">extern</td><td style="text-align:left">1.具有外部连接的静态存储期说明符 2.语言连接说明 3.显示模板实例化声明（或“extern 模板”）</td></tr><tr><td style="text-align:left">false</td><td style="text-align:left">布尔字面量</td></tr><tr><td style="text-align:left">float</td><td style="text-align:left">单精度浮点类型</td></tr><tr><td style="text-align:left">for</td><td style="text-align:left">用于该循环的声明</td></tr><tr><td style="text-align:left">friend</td><td style="text-align:left">友元声明出现于类体内，并向一个函数或另一个类授予对包含友元声明的类的私有及受保护成员的访问权。</td></tr><tr><td style="text-align:left">goto</td><td style="text-align:left">无条件转移控制流。当使用其他语句不可能将控制转移到所需位置时使用。</td></tr><tr><td style="text-align:left">if</td><td style="text-align:left">有条件地执行另一条语句。用于需要基于运行时或编译时条件执行的代码。</td></tr><tr><td style="text-align:left">inline</td><td style="text-align:left">1.将函数声明为一个 内联（inline）函数。完全在 class/struct/union 的定义之内定义的函数，无论它是成员函数还是非成员 friend 函数，均为隐式的内联函数。2.内联命名空间</td></tr><tr><td style="text-align:left">int</td><td style="text-align:left">基本整数类型</td></tr><tr><td style="text-align:left">long</td><td style="text-align:left">长整数类型</td></tr><tr><td style="text-align:left">mutable(1)</td><td style="text-align:left">1.容许在即便包含它的对象被声明为 const 时仍可修改声明为 mutable 的类成员。2.mutable 用于指定不影响类的外部可观察状态的成员（通常用于互斥体、记忆缓存、惰性求值和访问指令等）。3.从按复制捕获的形参中移除的 const 限定性的 lambda 声明符 (C++11 起) 4.被mutable修饰的变量，将永远处于可变的状态，即使在一个const函数中</td></tr><tr><td style="text-align:left">namespace</td><td style="text-align:left">命名空间声明</td></tr><tr><td style="text-align:left">new</td><td style="text-align:left">.创建并初始化拥有动态存储期的对象，这些对象的生存期不受它们创建时所在的作用域限制</td></tr><tr><td style="text-align:left">noexcept (C++11 起)</td><td style="text-align:left">1.noexcept 运算符进行编译时检查，若表达式声明为不抛出任何异常则返回 true 2.指定函数是否抛出异常</td></tr><tr><td style="text-align:left">not</td><td style="text-align:left">用作 <code>!</code> 的替代写法</td></tr><tr><td style="text-align:left">not_eq</td><td style="text-align:left">用作 <code>!=</code> 的替代写法</td></tr><tr><td style="text-align:left">nullptr (C++11 起)</td><td style="text-align:left">代表指针字面量，保持空指针常量的含义</td></tr><tr><td style="text-align:left">operator</td><td style="text-align:left">重载的运算符是具有特殊的函数名的函数</td></tr><tr><td style="text-align:left">or</td><td style="text-align:left">用作</td><td>的替代写法</td></tr><tr><td style="text-align:left">or_eq</td><td style="text-align:left">用作 `\</td><td>=` 的替代写法</td></tr><tr><td style="text-align:left">private</td><td style="text-align:left">该访问说明符之后的各个成员具有私有成员访问</td></tr><tr><td style="text-align:left">protected</td><td style="text-align:left">该访问说明符之后的各个成员具有受保护成员访问</td></tr><tr><td style="text-align:left">public</td><td style="text-align:left">该访问说明符之后的各个成员具有公开成员访问</td></tr><tr><td style="text-align:left">reflexpr (反射 TS)</td><td style="text-align:left">反射是指程序在运行时动态获取对象属性与方法的一种机制</td></tr><tr><td style="text-align:left">register</td><td style="text-align:left">1.动存储期，另提示编译器将此对象置于处理器的寄存器。2.不使用并保留该关键词</td></tr><tr><td style="text-align:left">reinterpret_cast</td><td style="text-align:left">通过重新解释底层位模式在类型间转换。</td></tr><tr><td style="text-align:left">requires (C++20 起)</td><td style="text-align:left">1.对模板形参指定一个求值为要求的常量表达式。 2.于模板声明中指定关联制约</td></tr><tr><td style="text-align:left">return</td><td style="text-align:left">终止当前函数并返回指定值（若存在）给调用方函数</td></tr><tr><td style="text-align:left">short</td><td style="text-align:left">至少 16 位的宽度（和long一样表示大小）</td></tr><tr><td style="text-align:left">signed</td><td style="text-align:left">目标类型将拥有有符号表示（若省略则此为默认）</td></tr><tr><td style="text-align:left">sizeof(1)</td><td style="text-align:left">1.查询对象或类型的大小。2.在需要知道对象的实际大小时使用。3.返回形参包中的元素数量</td></tr><tr><td style="text-align:left">static</td><td style="text-align:left">1.声明具有静态存储期和内部连接的命名空间成员。2.定义具有静态存储期且仅初始化一次的块作用域变量。3.声明不绑定到特定实例的类成员</td></tr><tr><td style="text-align:left">static_assert (C++11 起)</td><td style="text-align:left">进行编译时断言检查</td></tr><tr><td style="text-align:left">static_cast</td><td style="text-align:left">用隐式和用户定义转换的组合在类型间转换</td></tr><tr><td style="text-align:left">struct</td><td style="text-align:left">1.复合类型的声明。2.有作用域枚举类型的声明(C++11 起) 3.若存在于作用域中的某个函数或变量所拥有的名字，与某个非联合体类类型的名字相同，则可在其名字之前加上 struct 来消歧义，这产生一个详述类型说明符</td></tr><tr><td style="text-align:left">switch</td><td style="text-align:left">根据条件的值，将控制流转移到若干语句之一</td></tr><tr><td style="text-align:left">synchronized (TM TS)</td><td style="text-align:left">程序中的所有最外层同步块都以一个单独的全序执行</td></tr><tr><td style="text-align:left">template</td><td style="text-align:left">1.声明模板。 2.在模板定义中，template 可用于将某个待决名声明为模板</td></tr><tr><td style="text-align:left">this</td><td style="text-align:left">this 是一个纯右值表达式，其值是隐式对象形参（在其上调用非静态成员函数的对象）的地址</td></tr><tr><td style="text-align:left">thread_local (C++11 起)</td><td style="text-align:left">线程存储期</td></tr><tr><td style="text-align:left">throw</td><td style="text-align:left">1.对错误条件发信号，并执行错误处理代码 2.列出函数可能直接或间接抛出的异常</td></tr><tr><td style="text-align:left">true</td><td style="text-align:left">bool 类型的纯右值，为真</td></tr><tr><td style="text-align:left">try</td><td style="text-align:left">将一或多个异常处理块（catch 子句）与复合语句关联</td></tr><tr><td style="text-align:left">typedef</td><td style="text-align:left">创建能在任何位置替代（可能复杂的）类型名的别名</td></tr><tr><td style="text-align:left">typeid</td><td style="text-align:left">查询类型的信息。用于必须知晓多态对象的动态类型的场合以及静态类型鉴别</td></tr><tr><td style="text-align:left">typename</td><td style="text-align:left">1.在模板声明中，typename 可用作 class 的代替品，以声明类型模板形参和模板形参 (C++17 起) 2.在模板的声明或定义内，typename 可用于声明某个待决的有限定名是类型。3.在模板的声明或定义内， (C++11 前)typename 可在非待决的有限定类型名之前使用。此情况下它没有效果。4.在类型要求的要求中。(C++20 起)</td></tr><tr><td style="text-align:left">union</td><td style="text-align:left">1.联合体类型的声明。2.若存在于作用域中的某个函数或变量所拥有的名字与某个联合体类型的名字相同，则可在该名字之前加上 union 以消歧义，这产生详述类型说明符</td></tr><tr><td style="text-align:left">unsigned</td><td style="text-align:left">目标类型将拥有无符号表示</td></tr><tr><td style="text-align:left">using</td><td style="text-align:left">1.对命名空间的 using 指令及对命名空间成员的 using 声明。2.对类成员的 using 声明。3.类型别名与别名模板声明 (C++11 起)</td></tr><tr><td style="text-align:left">virtual</td><td style="text-align:left">1.虚函数说明符。2.虚基类说明符</td></tr><tr><td style="text-align:left">void</td><td style="text-align:left">1.void 类型。2.无形参函数的形参列表</td></tr><tr><td style="text-align:left">volatile</td><td style="text-align:left">定义类型为易变，确保本条指令不会因编译器的优化而省略，且要求每次直接读值</td></tr><tr><td style="text-align:left">wchar_t</td><td style="text-align:left">宽字符表示的类型</td></tr><tr><td style="text-align:left">while</td><td style="text-align:left">1.while 循环的声明。2.do-while 循环终止条件的声明</td></tr><tr><td style="text-align:left">xor</td><td style="text-align:left">作为 <code>^</code> 的替代写法</td></tr><tr><td style="text-align:left">xor_eq</td><td style="text-align:left">用作 <code>^=</code> 的替代写法</td></tr></tbody></table><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>C++关键字从C++11开始快速扩张，到C++20标准已经有100个左右了</li><li>C语言关键字发展到现在也有60多个了</li><li>像 <code>xor_eq</code>、<code>or_eq</code> 这些关键字就是对原来的运算符号换了个写法，感觉上没有什么用，只是不用敲特殊符号了</li></ul><hr><center><a href="https://blog.csdn.net/albertsh/article/details/133655454" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>好日子都是自己争取来的~</p><p>2023-10-7 23:34:23</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;源于上一篇中提到了 &lt;code&gt;decltype&lt;/code&gt;是一个关键字，所以去查了现有的关键字列表，其实我对C/C++的关键字还停留在上世纪大概30多个的时代，虽然知道C++加了 &lt;code&gt;auto&lt;/code&gt;、&lt;code&gt;constexpr&lt;/code&gt; 等关键字，但查询过后才发现原来他扩充到了将近100个，而C语言的关键字也扩到了六七十个，有点震惊了。&lt;/p&gt;
    
    </summary>
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/categories/C-C/"/>
    
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/tags/C-C/"/>
    
      <category term="keywords" scheme="http://AlbertGithubHome.github.io/blog/tags/keywords/"/>
    
      <category term="xor_eq" scheme="http://AlbertGithubHome.github.io/blog/tags/xor-eq/"/>
    
      <category term="co_await" scheme="http://AlbertGithubHome.github.io/blog/tags/co-await/"/>
    
  </entry>
  
  <entry>
    <title>linux环境下如果掌控了系统root账户就能对上面安装的MySQL数据为所欲为了吗</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/10/07/linux%E7%8E%AF%E5%A2%83%E4%B8%8B%E5%A6%82%E6%9E%9C%E6%8E%8C%E6%8E%A7%E4%BA%86%E7%B3%BB%E7%BB%9Froot%E8%B4%A6%E6%88%B7%E5%B0%B1%E8%83%BD%E5%AF%B9%E4%B8%8A%E9%9D%A2%E5%AE%89%E8%A3%85%E7%9A%84MySQL%E6%95%B0%E6%8D%AE%E4%B8%BA%E6%89%80%E6%AC%B2%E4%B8%BA%E4%BA%86%E5%90%97/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/10/07/linux环境下如果掌控了系统root账户就能对上面安装的MySQL数据为所欲为了吗/</id>
    <published>2023-10-07T12:47:41.000Z</published>
    <updated>2023-10-08T15:06:03.971Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>根据我目前测试的情况来看，这个问题的答案是【确实可以为所欲为】，事情的起因是这样的，一次偶然的机会我发现Ubuntu20.04系统上通过 <code>sudo apt install mysql-server</code> 命令安装的MySQL服务器，使用root账号登录无论输入什么密码都可以成功进入，多次尝试后才知道原来root用户默认没有密码，这可是让我惊呆了，居然还可以这样，为了服务器的安全，我必须给他设置个密码，在不断尝试的过程中引发了标题中的疑问。</p><a id="more"></a><h1 id="修改root密码"><a href="#修改root密码" class="headerlink" title="修改root密码"></a>修改root密码</h1><p>修改MySQL用户root密码在网络上充斥着各种各样的版本，最常见的这种在MySQL8.0中已经不生效了，错误示例如下：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">UPDATE</span> <span class="keyword">user</span> <span class="keyword">SET</span> authentication_string = <span class="keyword">PASSWORD</span>(<span class="string">'iL1jfLqUSfmTECXRi4nO'</span>) <span class="keyword">WHERE</span> <span class="keyword">User</span> = <span class="string">'root'</span>;</span><br></pre></td></tr></table></figure><p>目前测试在MySQL8.0版本中可行的设置root密码的方式为：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">USE</span> mysql;</span><br><span class="line"><span class="keyword">ALTER</span> <span class="keyword">USER</span> <span class="string">'root'</span>@<span class="string">'localhost'</span> <span class="keyword">IDENTIFIED</span> <span class="keyword">WITH</span> mysql_native_password <span class="keyword">BY</span> <span class="string">'iL1jfLqUSfmTECXRi4nO'</span>;</span><br><span class="line"><span class="keyword">FLUSH</span> <span class="keyword">PRIVILEGES</span>;</span><br></pre></td></tr></table></figure><h1 id="产生疑问"><a href="#产生疑问" class="headerlink" title="产生疑问"></a>产生疑问</h1><p>root用户的密码是设置好了，这么长的密码估计破解个几年也破解不开，但是我发现安装完毕提示了这样一样配置文件 <code>/etc/mysql/debian.cnf</code>，打开文件内容展示如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># Automatically generated for Debian scripts. DO NOT TOUCH!</span><br><span class="line">[client]</span><br><span class="line">host     = localhost</span><br><span class="line">user     = debian-sys-maint</span><br><span class="line">password = xg9vyvjNly3vUSsm</span><br><span class="line">socket   = /var/run/mysqld/mysqld.sock</span><br><span class="line">[mysql_upgrade]</span><br><span class="line">host     = localhost</span><br><span class="line">user     = debian-sys-maint</span><br><span class="line">password = xg9vyvjNly3vUSsm</span><br><span class="line">socket   = /var/run/mysqld/mysqld.sock</span><br></pre></td></tr></table></figure><p>居然是一个包含了MySQL用户和密码的未加密的文本文件，这不是在大门口的脚垫底下放上了大门的钥匙吗？关键这个debian-sys-maint虽然设计上是用于数据库维护和管理，但权限上与root几乎相同，通常在 Debian 或基于 Debian 的 Linux 发行版比如Ununtu上安装MySQL就会自动生成这个文件，看起来有点不安全啊？</p><p>于是查了相关的处理方法，都说要控制这个文件的权限，只让系统root用户可以访问它，这时我就产生了疑问，这样做安全吗？如果系统root用户被窃取了呢？那是不是完全可以通过这个文件来操控MySQL数据了，答案是很显然的。</p><h1 id="渐渐明白"><a href="#渐渐明白" class="headerlink" title="渐渐明白"></a>渐渐明白</h1><p>虽然说掌握了root用户就可以任意修改MySQL数据让人难以接受，但是换个角度或许就可以释然了，root可是系统里的上帝啊，控制了root用户可以任意执行 <code>rm -rf /*</code>，如果你知道银行卡密码当然就可以从取款机里取钱，对于操作系统也是一样，既然你是root你就掌控了这个系统里的一切。</p><p>即便没有这个<code>/etc/mysql/debian.cnf</code>文件，root用户还可以先关闭MySQL服务，然后通过 <code>sudo mysqld_safe --skip-grant-tables &amp;</code>以跳过授权表的方式启动MySQL，此时允许你在不知道密码的情况下以 root 身份登录 MySQL，这还有什么隐私可言。再换种方式，作为root用户的我从磁盘上把MySQL存储的文件拿到其他MySQL服务器上都是可以的，所以你是防不住root上帝的。</p><p>所以刚刚那个【大门口的脚垫底下放上了大门的钥匙】的比喻不太恰当，应该是<code>/etc/mysql/debian.cnf</code>就像屋里的桌子上摆着的保险柜的钥匙，而你要保证大门的钥匙（root密码）不要轻易被人搞到手，像 <code>--skip-grant-tables</code> 这种看起来很像是后门，或者叫应急开关，实际上它的设计就是为了提供一种紧急的修复机制，用于在丧失所有密码或无法登录到 MySQL 数据库时进行数据库恢复和修复，比如紧急恢复、密码重置、修复数据库权限等等</p><p>这种设计就像是各司其职，我只负责我数据的管理与权限的分配，而用户需要保证系统很牢固，一旦你的系统被攻破我将束手就擒，做的再多也是徒劳。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>MySQL8.0修改密码的方式<code>ALTER USER &#39;root&#39;@&#39;localhost&#39; IDENTIFIED WITH mysql_native_password BY &#39;xxx&#39;;</code></li><li>文件<code>/etc/mysql/debian.cnf</code>是Debian系列系统上安装MySQL时自动生成的，其中的用户用于用于数据库维护和管理</li><li>如果忘了MySQL中root用户的密码可以通过 <code>sudo mysqld_safe --skip-grant-tables &amp;</code> 启动服务来进行免密登录</li><li>控制了系统的root用户，你就掌控了操作系统的一切</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/132641641" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>假期归来又看了很多，也不安分了许多，或许只有通过打破常规才能获得额外的收益，否则只能是一直保持饿不死的状态</p><p>2023-10-7 20:47:41</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;根据我目前测试的情况来看，这个问题的答案是【确实可以为所欲为】，事情的起因是这样的，一次偶然的机会我发现Ubuntu20.04系统上通过 &lt;code&gt;sudo apt install mysql-server&lt;/code&gt; 命令安装的MySQL服务器，使用root账号登录无论输入什么密码都可以成功进入，多次尝试后才知道原来root用户默认没有密码，这可是让我惊呆了，居然还可以这样，为了服务器的安全，我必须给他设置个密码，在不断尝试的过程中引发了标题中的疑问。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/tags/linux/"/>
    
      <category term="ubuntu" scheme="http://AlbertGithubHome.github.io/blog/tags/ubuntu/"/>
    
      <category term="mysql" scheme="http://AlbertGithubHome.github.io/blog/tags/mysql/"/>
    
      <category term="root" scheme="http://AlbertGithubHome.github.io/blog/tags/root/"/>
    
      <category term="debian-sys-maint" scheme="http://AlbertGithubHome.github.io/blog/tags/debian-sys-maint/"/>
    
      <category term="skip-grant-tables" scheme="http://AlbertGithubHome.github.io/blog/tags/skip-grant-tables/"/>
    
  </entry>
  
  <entry>
    <title>C++中的decltype、std::declval 和 std::decay_t傻傻分不清楚</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/09/05/C-%E4%B8%AD%E7%9A%84decltype%E3%80%81std-declval-%E5%92%8C-std-decay-t%E5%82%BB%E5%82%BB%E5%88%86%E4%B8%8D%E6%B8%85%E6%A5%9A/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/09/05/C-中的decltype、std-declval-和-std-decay-t傻傻分不清楚/</id>
    <published>2023-09-05T13:42:25.000Z</published>
    <updated>2023-10-08T15:02:51.293Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在C++中提到推导第一个映入脑海的可能是“模板”，当然有人也可能想到 <code>auto</code>，这些都是和推导相关的语言语法，再比如“完美转发”等等，总是就是他们的类型不用明明白白的写出来，可以利用一种简单的方式来定义，比如 <code>candidates</code> 是一个字符串数组， 如果遍历它我们可以写成 <code>for (auto&amp; skillId : candidates)</code>，而不用将 <code>skillId</code> 变量定义成 <code>const std::string&amp;</code>，这在一定程度上带来了便利，今天的几个东东也是用于推导的，我们一起来看看。</p><a id="more"></a><h1 id="它们是什么"><a href="#它们是什么" class="headerlink" title="它们是什么"></a>它们是什么</h1><p><code>decltype</code>、<code>std::declval</code> 和 <code>std::decay_t</code> 都是 C++ 标准库中的类型相关工具，这些工具在 C++ 编程中提供了强大的类型推导和处理能力，使得代码更加灵活。</p><ul><li><p><code>decltype</code> 是一个关键字，用于从一个表达式中推导出其类型。它通常与表达式一起使用，以便在编译时确定表达式的类型。例如：</p> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> x = <span class="number">5</span>;</span><br><span class="line"><span class="keyword">decltype</span>(x) y = <span class="number">10</span>;  <span class="comment">// y 的类型将被推导为 int</span></span><br></pre></td></tr></table></figure><p> <code>decltype</code> 还可以用于推导函数返回类型，以及在泛型编程中处理模板的类型。</p></li><li><p><code>std::declval</code> 是一个函数模板，用于在不实际调用构造函数的情况下生成一个对象引用，以便在类型推导中使用。通常在需要引用某个类型的对象但实际无法创建该对象的上下文中使用，例如在函数返回类型推导中：</p> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line">auto createAndProcess() -&gt; decltype(std::declval&lt;T&gt;().process()) &#123;</span><br><span class="line">    <span class="comment">// 此处只是用于类型推导，并不会实际创建 T 的对象</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 在这个例子中，<code>std::declval&lt;T&gt;().process()</code> 用于推导函数的返回类型，但并不会实际调用 <code>process</code> 函数。</p></li><li><p><code>std::decay_t</code> 是一个类型转换工具模板，用于将给定类型的”衰变”后的类型返回。衰变指的是将类型转换为其”自然”形式，即去掉引用、添加 cv 限定符（const 和 volatile）。例如：</p> <figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">using</span> T1 = <span class="built_in">std</span>::<span class="keyword">decay_t</span>&lt;<span class="keyword">int</span>&amp;&gt;;      <span class="comment">// T1 为 int</span></span><br><span class="line"><span class="keyword">using</span> T2 = <span class="built_in">std</span>::<span class="keyword">decay_t</span>&lt;<span class="keyword">const</span> <span class="keyword">int</span>&gt;; <span class="comment">// T2 为 int</span></span><br><span class="line"><span class="keyword">using</span> T3 = <span class="built_in">std</span>::<span class="keyword">decay_t</span>&lt;<span class="keyword">int</span>[]&gt;;     <span class="comment">// T3 为 int*</span></span><br></pre></td></tr></table></figure><p> <code>std::decay_t</code> 在模板编程中很有用，因为它可以帮助去除类型的多余限定符，使得函数模板的匹配更加精确。</p></li></ul><h1 id="通俗解释"><a href="#通俗解释" class="headerlink" title="通俗解释"></a>通俗解释</h1><p>如果看了上面的例子还是有点迷糊，那我再解释的通俗一点：</p><ul><li><p><code>decltype</code>  读音为 “dee-kluh-type”，就是返回一个变量的类型，比如你有一个对象 <code>obj</code> 想要再定义一个和它相同各类型的变量就可以写成 <code>decltype(obj) newObj;</code></p></li><li><p><code>std::declval</code>  读音为 “standard dee-cl-val”，它能返回类型 T 的右值引用，其实是一个伪实例，不会产生任何临时对象，也不会因为表达式很复杂而发生真实的计算。因为不会真正的进行构造，所以可以实现在元编程时伪构造一个没有定义默认构造函数类，还可以避开纯虚基类不能实例化的问题，说白了它就是假装创建个对象用于推导类型。</p></li><li><p><code>std::decay_t</code> 读音为 “standard dee-kay tee”，有点褪去外表直击内心的意味，就是大家一起褪去花里胡哨的修饰符，获得原始的类型，用于函数模板的匹配更加精确，比如判断类型 <code>T</code>是不是int，<code>if constexpr (std::is_same_v&lt;std::decay_t&lt;T&gt;, int&gt;)</code></p></li></ul><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li><code>decltype</code> 是关键字，用于有了值或对象求类型的</li><li><code>std::declval</code> 是函数模板，用于伪造一个对象但不实际构造的</li><li><code>std::decay_t</code> 是类模板，用于去除修饰符获取原始类型的</li><li>没想到<code>decltype</code> 居然是关键字，结果一查发现C++的关键字已经膨胀了，下次总结一下</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/132549001" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>当身边出现不和谐声音的时候问问自己，是不是你把他们招来的，用实力让他们闭嘴，而不要视图通过语言让他们明白</p><p>2023-9-5 21:42:25</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;在C++中提到推导第一个映入脑海的可能是“模板”，当然有人也可能想到 &lt;code&gt;auto&lt;/code&gt;，这些都是和推导相关的语言语法，再比如“完美转发”等等，总是就是他们的类型不用明明白白的写出来，可以利用一种简单的方式来定义，比如 &lt;code&gt;candidates&lt;/code&gt; 是一个字符串数组， 如果遍历它我们可以写成 &lt;code&gt;for (auto&amp;amp; skillId : candidates)&lt;/code&gt;，而不用将 &lt;code&gt;skillId&lt;/code&gt; 变量定义成 &lt;code&gt;const std::string&amp;amp;&lt;/code&gt;，这在一定程度上带来了便利，今天的几个东东也是用于推导的，我们一起来看看。&lt;/p&gt;
    
    </summary>
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/categories/C-C/"/>
    
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/tags/C-C/"/>
    
      <category term="decltype" scheme="http://AlbertGithubHome.github.io/blog/tags/decltype/"/>
    
      <category term="std::declval" scheme="http://AlbertGithubHome.github.io/blog/tags/std-declval/"/>
    
      <category term="std::decay_t" scheme="http://AlbertGithubHome.github.io/blog/tags/std-decay-t/"/>
    
  </entry>
  
  <entry>
    <title>C++定义一个返回值为拥有3个元素的 char 数组引用的函数</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/08/28/C-%E5%AE%9A%E4%B9%89%E4%B8%80%E4%B8%AA%E8%BF%94%E5%9B%9E%E5%80%BC%E4%B8%BA%E6%8B%A5%E6%9C%893%E4%B8%AA%E5%85%83%E7%B4%A0%E7%9A%84-char-%E6%95%B0%E7%BB%84%E5%BC%95%E7%94%A8%E7%9A%84%E5%87%BD%E6%95%B0/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/08/28/C-定义一个返回值为拥有3个元素的-char-数组引用的函数/</id>
    <published>2023-08-28T14:21:41.000Z</published>
    <updated>2023-09-03T12:57:59.425Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>编程语言最怕细节，怼着一个点扣能让你怀疑人生，今天就碰到这么一个问题，“C++定义一个返回值为拥有3个元素的 char 数组引用的函数”，不敢说99%的人不会，但我觉得应该有80%的人写不出来吧，要不你来试试。</p><a id="more"></a><h1 id="分解需求"><a href="#分解需求" class="headerlink" title="分解需求"></a>分解需求</h1><p>首先得能看出来这是要求定义一个函数，如果这一步就错了，那就没必要往后走了，函数有的参数没有要求，但是返回值得是一个引用，这个引用的原始类型应该为一个拥有3个元素的char类型数组，难道要这样写？</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">char</span>&amp; [<span class="number">3</span>] func()</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>试试吧，肯定编不过的，要是这么容易就哪能让人死去活来，直接给答案了</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line">const char (&amp;getCharArray())[3] &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">const</span> <span class="keyword">char</span> charArray[<span class="number">3</span>] = &#123;<span class="string">'A'</span>, <span class="string">'B'</span>, <span class="string">'X'</span>&#125;;</span><br><span class="line">    <span class="keyword">return</span> charArray;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    const char (&amp;result)[3] = getCharArray();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">char</span> c : result) &#123;</span><br><span class="line">        <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; c &lt;&lt; <span class="string">" "</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="举一反三"><a href="#举一反三" class="headerlink" title="举一反三"></a>举一反三</h1><p>学习要学会举一反三，把题目中的“引用”换成“指针”，再试一下，依葫芦画瓢还是很容易的</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span> (*getCharArrayPointer())[<span class="number">3</span>] &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">char</span> charArray[<span class="number">3</span>] = &#123;<span class="string">'A'</span>, <span class="string">'B'</span>, <span class="string">'J'</span>&#125;;</span><br><span class="line">    <span class="keyword">return</span> &amp;charArray;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">char</span> (*result)[<span class="number">3</span>] = getCharArrayPointer();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">3</span>; ++i) &#123;</span><br><span class="line">        <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; (*result)[i] &lt;&lt; <span class="string">" "</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>仿佛这也不是我们常常见到的形式，对于指针作为返回值，我们常见到的应该是下面这种</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">char</span>* <span class="title">getCharArrayPointer</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">char</span> charArray[<span class="number">10</span>] = &#123;<span class="string">'H'</span>, <span class="string">'e'</span>, <span class="string">'l'</span>, <span class="string">'l'</span>, <span class="string">'o'</span>, <span class="string">' '</span>, <span class="string">'W'</span>, <span class="string">'o'</span>, <span class="string">'r'</span>, <span class="string">'l'</span>&#125;;</span><br><span class="line">    <span class="keyword">return</span> charArray;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">char</span>* result = getCharArrayPointer();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; ++i) &#123;</span><br><span class="line">        <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; result[i];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>他们有什么区别呢？我们从定义层面来对比一下：</p><h1 id="进行对比"><a href="#进行对比" class="headerlink" title="进行对比"></a>进行对比</h1><ul><li><figure class="highlight plain"><figcaption><span>char (&result)[3] </span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">  - `result` 是一个引用类型变量，被引用的类型为 `char[3]`</span><br><span class="line">  - `sizeof(result)` == 3</span><br><span class="line">- ```char (*result)[3] = getCharArrayPointer();</span><br></pre></td></tr></table></figure><ul><li><code>result</code> 是一个指针类型变量，指向的类型的类型为 <code>char[3]</code></li><li><code>sizeof(result)</code> == 8</li><li><code>result++</code> 先后移动24个字节=8*3</li></ul></li><li><code>char* result = getCharArrayPointer();</code><ul><li><code>result</code> 是一个指针类型变量，被引用的类型为 <code>char</code></li><li><code>sizeof(result)</code> == 8</li><li><code>result++</code> 先后移动8个字节=8*1</li></ul></li></ul><p>有了上面的对比就可以根据区别来选择定义什么样的返回值，使用引用类型能取到被引用变量的原始大小，使用指针要注意移动的步长。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>定义一个返回定长数组引用的函数 <code>const char (&amp;getCharArray())[3] { //... }</code></li><li>定义一个返回定长数组指针的函数 <code>const char (*getCharArray())[3] { //... }</code></li><li>引用不需要内存分配和释放，因为它只是目标对象的别名（存疑，在绝大多数编译器中，引用在底层是通过指针来实现的，但GPT嘴硬说不需要，表示需要继续探索）</li></ul><blockquote><p>引用在底层通过指针实现时，并不需要额外的内存分配。引用被设计为变量的别名，它并不占用额外的内存空间。引用在编译器生成的汇编代码中，会被处理为指向原始对象的指针，但是这个指针对于用户来说是透明的，不需要分配额外的内存来存储引用本身。<br>引用只是目标对象的别名，与指针不同，它没有自己的内存存储空间。因此，无论引用在底层是通过指针实现还是其他方式实现，都不会导致额外的内存分配。这也是为什么引用在使用上更接近于对变量的直接访问。<br>需要注意的是，尽管引用不需要额外的内存分配，但指针本身需要存储指向对象的地址，因此指针会占用一定的内存空间。这也是引用和指针之间的一个重要区别。</p></blockquote><center><a href="https://blog.csdn.net/albertsh/article/details/132484006" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>内练一口气，外练筋骨皮</p><p>2023-8-28 22:17:27</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;编程语言最怕细节，怼着一个点扣能让你怀疑人生，今天就碰到这么一个问题，“C++定义一个返回值为拥有3个元素的 char 数组引用的函数”，不敢说99%的人不会，但我觉得应该有80%的人写不出来吧，要不你来试试。&lt;/p&gt;
    
    </summary>
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/categories/C-C/"/>
    
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/tags/C-C/"/>
    
      <category term="开发语言" scheme="http://AlbertGithubHome.github.io/blog/tags/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/"/>
    
      <category term="指针" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%8C%87%E9%92%88/"/>
    
      <category term="引用" scheme="http://AlbertGithubHome.github.io/blog/tags/%E5%BC%95%E7%94%A8/"/>
    
      <category term="函数定义" scheme="http://AlbertGithubHome.github.io/blog/tags/%E5%87%BD%E6%95%B0%E5%AE%9A%E4%B9%89/"/>
    
  </entry>
  
  <entry>
    <title>C++编程中的六种内存顺序模型</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/08/24/C-%E7%BC%96%E7%A8%8B%E4%B8%AD%E7%9A%84%E5%85%AD%E7%A7%8D%E5%86%85%E5%AD%98%E9%A1%BA%E5%BA%8F%E6%A8%A1%E5%9E%8B/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/08/24/C-编程中的六种内存顺序模型/</id>
    <published>2023-08-24T15:01:59.000Z</published>
    <updated>2023-09-03T12:55:58.237Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>程序员真是一个活到老学到老的职业，一天不学习就会掉队，『内存顺序模型』对于我来说就是一个新的世界，虽然之前写过多线程的服务器，也处理过死锁和竞态条件等问题，但是从来没考虑过内存顺序问题，所以当我第一次看到这个概念时，整个人都是懵的，经过一段时间的学习和了解有了初步的认识，所以简单总结下来，以备后续查看，不多写，慢慢总结。</p><a id="more"></a><h1 id="为什么要设计内存顺序模型"><a href="#为什么要设计内存顺序模型" class="headerlink" title="为什么要设计内存顺序模型"></a>为什么要设计内存顺序模型</h1><p>内存顺序模型是为了解决多线程程序中的内存一致性和可见性问题而引入的。在多线程环境下，不同线程可能同时访问和修改共享的内存，这会引发一系列并发性问题，如竞态条件、数据竞争等。内存顺序模型的目的是通过定义不同操作之间的执行顺序和可见性规则，来保证多线程程序的正确性和可预测性。主要原因如下：</p><ol><li><p><strong>多线程并发问题：</strong> 在多线程程序中，线程之间可能并发地读取和写入共享内存，导致数据不一致和不可预测的行为。</p></li><li><p><strong>编译器和处理器优化：</strong> 编译器和处理器可能会对代码进行优化，例如重排指令以提高性能。这些优化可能会导致操作的执行顺序与代码中的顺序不一致，从而引发问题。</p></li><li><p><strong>硬件内存模型：</strong> 不同的计算机体系结构有不同的硬件内存模型，即不同的读写操作在不同的条件下可能表现出不同的行为。</p></li><li><p><strong>数据依赖性：</strong> 在某些情况下，某个操作的结果可能会影响后续操作的执行。内存顺序模型可以帮助定义这种数据依赖性。</p></li></ol><p>内存顺序模型通过定义不同操作之间的关系，如同步、重排等，来解决上述问题。不同的内存顺序模型提供了不同的可见性和同步保证，开发者可以根据自己的需求选择适当的模型。总之，内存顺序模型是为了在多线程环境下提供一种标准化的方式来处理内存一致性和可见性问题，从而使多线程编程更加可靠和可预测，重点关注下第2点和第4点。</p><p>有人可能会说，我可以用锁来保证顺序，为什么还要设计内存顺序模型呢？</p><p>虽然锁（比如互斥锁）在多线程编程中是一种常见的同步机制，用于保护共享资源，但锁并不能解决所有的并发性问题，而且在某些情况下使用锁可能会引入性能问题。内存顺序模型的设计是为了在不同线程之间定义操作的执行顺序和可见性规则，以解决锁无法解决的一些问题，同时在一些情况下也可以提高性能。</p><ol><li><p><strong>细粒度同步：</strong> 锁通常是用于保护共享资源的，但有时候我们需要更细粒度的同步，比如在不涉及共享资源的情况下也需要保证操作的顺序和可见性。</p></li><li><p><strong>原子操作：</strong> 内存顺序模型通过定义原子操作的执行顺序和可见性，可以在不使用锁的情况下确保操作的正确执行。这在一些场景下可以避免锁带来的性能开销。</p></li><li><p><strong>锁的开销：</strong> 锁在某些情况下可能引入较大的性能开销，特别是在高并发环境中。内存顺序模型提供了一种更轻量级的同步机制，可以在一些情况下取代锁，提高性能。</p></li><li><p><strong>编译器和处理器优化：</strong> 编译器和处理器对代码进行优化时可能会引入指令重排，这可能会导致锁保护下的共享资源出现问题。内存顺序模型通过规定操作的执行顺序，可以避免这种问题。</p></li><li><p><strong>原子操作的组合：</strong> 内存顺序模型的原子操作可以灵活地组合，以实现更复杂的同步和顺序要求，而不必仅仅依赖于锁。</p></li></ol><p>总之，内存顺序模型和锁在多线程编程中都有其适用的场景。锁通常用于保护共享资源的访问，而内存顺序模型则用于定义操作的执行顺序和可见性，以确保多线程程序的正确性。在多线程编程中，根据具体需求和性能要求，可以选择合适的同步机制。</p><p><strong>如果想理解内存顺序，首先要理解两个东西：同一线程中，谁先执行，谁后执行；不同线程中，切换内存的时是否会及时的把依赖数据带过去，对另一个线程可见。</strong></p><h1 id="常见的内存顺序模型"><a href="#常见的内存顺序模型" class="headerlink" title="常见的内存顺序模型"></a>常见的内存顺序模型</h1><p>C++ 标准库中定义了六种内存顺序模型，用于控制多线程程序中不同操作之间的执行顺序和可见性。这些内存顺序模型通过枚举值表示，从“<strong>宽松</strong>”到“<strong>严格</strong>”的次序分别是：</p><ol><li><p><strong><code>std::memory_order_relaxed</code>：</strong> 这是最轻量级的内存顺序模型。它不会引入任何额外的同步开销，只保证操作在时间上的顺序是正确的。即使没有明确的同步操作，也不会改变其他线程看到的操作结果。</p></li><li><p><strong><code>std::memory_order_consume</code>：</strong> 在 C++11 中引入，但在 C++20 中被弃用。它主要用于处理数据依赖关系，但在实际中难以实现，已经不推荐使用。</p></li><li><p><strong><code>std::memory_order_acquire</code>：</strong> 在执行当前操作之前，确保所有前面的读操作都完成。它提供了一种读操作同步的保证，确保读操作的结果在后续操作中是可见的。</p></li><li><p><strong><code>std::memory_order_release</code>：</strong> 在执行当前操作之后，确保所有后面的写操作都不会重排到当前操作之前。它提供了一种写操作同步的保证，确保写操作的结果对其他线程是可见的。</p></li><li><p><strong><code>std::memory_order_acq_rel</code>：</strong> 是 <code>memory_order_acquire</code> 和 <code>memory_order_release</code> 的组合。它同时提供了读和写操作的同步保证，适用于需要同时保证读写操作同步的情况。</p></li><li><p><strong><code>std::memory_order_seq_cst</code>：</strong> 是最严格的内存顺序模型，提供了全局的、顺序一致的保证。它确保所有操作按照一个全局的顺序执行，不会引入重排，也保证了最高级别的可见性。</p></li></ol><p>根据具体的多线程编程需求，你可以选择适当的内存顺序模型，以确保正确性和性能。不同的内存顺序模型会引入不同程度的同步开销，因此需要根据实际情况权衡选择。了解这些内存顺序模型可以帮助你在多线程编程中更好地控制操作的执行顺序和可见性，每种模型具体的例子我们后面再慢慢总结。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>常见的内存顺序模型前三种：<code>std::memory_order_relaxed</code>、<code>std::memory_order_acquire</code>、<code>std::memory_order_acquire</code></li><li>常见的内存顺序模型后三种：<code>std::memory_order_release</code>、<code>std::memory_order_acq_rel</code>、<code>std::memory_order_seq_cst</code></li><li>其中 `std::memory_order_consume`` 在实际中难以实现，已经在 C++20 中被弃用</li><li>编译器和处理器对代码进行优化时可能会引入指令重排，这可能会导致锁保护下的共享资源出现问题</li><li>内存顺序模型通过规定操作的执行顺序，可以避免上一个问题</li><li>内存顺序和原子操作的引入，是为了无锁的并发编程，提高并发编程的效率</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/132252530" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>或许我就不在你规划的未来<br>或许她才是你唯一的偏爱<br>或许我就不该跟你赌未来<br>或许就不会深陷苦海</p><p>2023-8-24 22:57:11</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;程序员真是一个活到老学到老的职业，一天不学习就会掉队，『内存顺序模型』对于我来说就是一个新的世界，虽然之前写过多线程的服务器，也处理过死锁和竞态条件等问题，但是从来没考虑过内存顺序问题，所以当我第一次看到这个概念时，整个人都是懵的，经过一段时间的学习和了解有了初步的认识，所以简单总结下来，以备后续查看，不多写，慢慢总结。&lt;/p&gt;
    
    </summary>
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/categories/C-C/"/>
    
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/tags/C-C/"/>
    
      <category term="内存顺序" scheme="http://AlbertGithubHome.github.io/blog/tags/%E5%86%85%E5%AD%98%E9%A1%BA%E5%BA%8F/"/>
    
      <category term="无锁编程" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%97%A0%E9%94%81%E7%BC%96%E7%A8%8B/"/>
    
      <category term="指令重排" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%8C%87%E4%BB%A4%E9%87%8D%E6%8E%92/"/>
    
  </entry>
  
  <entry>
    <title>linux环境下Shell脚本中定义函数</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/08/15/linux%E7%8E%AF%E5%A2%83%E4%B8%8BShell%E8%84%9A%E6%9C%AC%E4%B8%AD%E5%AE%9A%E4%B9%89%E5%87%BD%E6%95%B0/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/08/15/linux环境下Shell脚本中定义函数/</id>
    <published>2023-08-15T14:46:29.000Z</published>
    <updated>2023-09-03T12:54:02.752Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>说实话，这是我第一次用Shell来定义函数，之前也写了很多shell脚本，但是体量都不大，所以基本上是按照需求罗列了多个命令来实现的，这次也是一样，但是我发现重复的地方太多了，所以还是要拿出看见本事“抽象函数”来解放我懒惰的手指</p><a id="more"></a><h1 id="具体需求"><a href="#具体需求" class="headerlink" title="具体需求"></a>具体需求</h1><blockquote><p>检测特定名称的进程是否存在，若不存在则重新启动这个进程</p></blockquote><p>仿照着之前的类似的脚本我写出了以下函数 <code>check_and_fix_process</code>，仅有一个需要注意的点就是Shell函数不需要定义参数，如果用到参数直接是用 <code>$1</code>、 <code>$2</code>、 <code>$3</code>……依次来代替就可以了</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查并修复进程的函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="function"><span class="title">check_and_fix_process</span></span>() &#123;</span><br><span class="line">        <span class="comment"># $1 进程名</span></span><br><span class="line">        <span class="comment"># $2 进程ID列表文件名</span></span><br><span class="line">        <span class="comment"># $3 进程ID列表文件md5值</span></span><br><span class="line"></span><br><span class="line">        datetime=$(date +%y%m%d%H%M%S)</span><br><span class="line">        ps aux |grep <span class="string">"<span class="variable">$1</span>"</span> |grep -v grep |awk <span class="string">'&#123;print $15&#125;'</span> |awk -F <span class="string">'='</span> <span class="string">'&#123;print $2&#125;'</span>|sort -n &gt; ~/tmplist</span><br><span class="line"></span><br><span class="line">        a=$(md5sum ~/tmplist |awk <span class="string">'&#123;print $1&#125;'</span>)</span><br><span class="line">        b=<span class="string">"<span class="variable">$3</span>"</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> [[ <span class="variable">$a</span> = <span class="variable">$b</span> ]]; <span class="keyword">then</span></span><br><span class="line">                <span class="built_in">echo</span> <span class="string">"check [<span class="variable">$1</span> <span class="variable">$2</span> <span class="variable">$3</span>] ok"</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">                tmp=$(diff ~/<span class="string">"<span class="variable">$2</span>"</span> ~/tmplist |grep ^\&lt; |awk <span class="string">'&#123;print $2&#125;'</span> &gt; /tmp/tmplist)</span><br><span class="line">                <span class="keyword">for</span> i <span class="keyword">in</span> `cat /tmp/tmplist`</span><br><span class="line">                <span class="keyword">do</span></span><br><span class="line">                        <span class="built_in">cd</span> ~/<span class="built_in">test</span> &amp;&amp; grep <span class="string">"ID=<span class="variable">$i</span>"</span> start.sh | xargs -I &#123;&#125; sh -c <span class="string">'&#123;&#125;'</span></span><br><span class="line"></span><br><span class="line">                        <span class="built_in">echo</span> <span class="string">"<span class="variable">$datetime</span> <span class="variable">$i</span>"</span> &gt;&gt;~/fixtxt</span><br><span class="line">                        python3 ~/send_msg.py -c <span class="string">"process:<span class="variable">$i</span> crashed; fixtime:<span class="variable">$datetime</span>"</span></span><br><span class="line">                <span class="keyword">done</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># check all process list</span></span><br><span class="line">check_and_fix_process <span class="string">"AProcess"</span> <span class="string">"aprocesslist"</span> <span class="string">"a887eedb72472f2ad152e5e91a0727da"</span></span><br><span class="line">check_and_fix_process <span class="string">"BProcess"</span> <span class="string">"bprocesslist"</span> <span class="string">"a887eedb72472f2ad152e5e91a0727db"</span></span><br></pre></td></tr></table></figure><h1 id="ChatGPT读代码"><a href="#ChatGPT读代码" class="headerlink" title="ChatGPT读代码"></a>ChatGPT读代码</h1><p>如果对于这段代码有不理解的地方，可以请ChatGPT来帮忙，以下内容是它的解释，不得不说真是太强大了！身为此段代码的实现者，我都描述不了这么详细</p><hr><p>这段代码是一个 Bash 脚本，用于检查指定的进程是否正常运行，并在发现异常时进行修复操作。下面逐行解释代码：</p><ol><li><p><code>function check_and_fix_process() {</code>: 定义了一个名为 <code>check_and_fix_process</code> 的 Bash 函数。</p></li><li><p><code># $1 进程名</code>: 这是一个注释，表示函数的第一个参数是进程名。</p></li><li><p><code># $2 进程ID列表文件名</code>: 这是一个注释，表示函数的第二个参数是进程ID列表文件的文件名。</p></li><li><p><code># $3 进程ID列表文件md5值</code>: 这是一个注释，表示函数的第三个参数是进程ID列表文件的MD5值。</p></li><li><p><code>datetime=$(date +%y%m%d%H%M%S)</code>: 获取当前日期和时间，并将其格式化为”YYMMDDHHMMSS”的形式，存储在变量 <code>datetime</code> 中。</p></li><li><p><code>ps aux |grep &quot;$1&quot; |grep -v grep |awk &#39;{print $15}&#39; |awk -F &#39;=&#39; &#39;{print $2}&#39;|sort -n &gt; ~/tmplist</code>: 这一行通过一系列的管道命令来查找指定进程名 <code>$1</code> 的进程，提取其中的某个列（可能是进程ID），然后排序，将结果保存到文件 <code>~/tmplist</code> 中。</p></li><li><p><code>a=$(md5sum ~/tmplist |awk &#39;{print $1}&#39;)</code>: 计算文件 <code>~/tmplist</code> 的 MD5 值，并将结果保存在变量 <code>a</code> 中。</p></li><li><p><code>b=&quot;$3&quot;</code>: 将第三个参数值（进程ID列表文件的MD5值）保存在变量 <code>b</code> 中。</p></li><li><p><code>if [[ $a = $b ]]; then</code>: 如果变量 <code>a</code> 的值等于变量 <code>b</code> 的值（即进程ID列表文件的MD5值相匹配），则执行下面的代码块。</p></li><li><p><code>echo &quot;check [$1 $2 $3] ok&quot;</code>: 输出一条信息表示检查通过。</p></li><li><p><code>else</code>: 如果进程ID列表文件的MD5值不匹配，则执行下面的代码块。</p></li><li><p><code>tmp=$(diff ~/&quot;$2&quot; ~/tmplist |grep ^\&lt; |awk &#39;{print $2}&#39; &gt; /tmp/tmplist)</code>: 比较进程ID列表文件（路径为 <code>~/&quot;$2&quot;</code>)和当前的进程ID列表文件 (<code>~/tmplist</code>)，将不匹配的部分提取出来并保存在临时文件 <code>/tmp/tmplist</code> 中。</p></li><li><p><code>for i in $(cat /tmp/tmplist)</code>: 循环遍历临时文件 <code>/tmp/tmplist</code> 中的每一行内容。</p></li><li><p><code>do</code>: 循环体开始。</p></li><li><p><code>cd ~/test &amp;&amp; grep &quot;ID=$i&quot; start.sh | xargs -I {} sh -c &#39;{}&#39;</code>: 这一行的作用是切换到目录 <code>~/test</code> 并在 <code>start.sh</code> 文件中查找包含字符串 “ID=$i” 的行，然后执行找到的行（通常是一些命令）。</p></li><li><p><code>echo &quot;$datetime $i&quot; &gt;&gt;~/fixtxt</code>: 将当前日期时间和进程ID添加到文件 <code>~/fixtxt</code> 中。</p></li><li><p><code>python3 ~/send_msg.py -c &quot;process:$i crashed; fixtime:$datetime&quot;</code>: 调用 Python 脚本 <code>send_msg.py</code> 并传递一条消息，该消息包含进程ID以及修复操作的时间。</p></li><li><p><code>done</code>: 循环结束。</p></li><li><p><code>fi</code>: 条件分支结束。</p></li><li><p><code>}</code>: 函数定义结束。</p></li><li><p><code>check_and_fix_process &quot;AProcess&quot; &quot;aprocesslist&quot; &quot;a887eedb72472f2ad152e5e91a0727da&quot;</code>: 调用 <code>check_and_fix_process</code> 函数，检查名为 “AProcess” 的进程是否正常运行，使用文件 “aprocesslist” 作为进程ID列表文件，并传递预期的MD5值。</p></li><li><p><code>check_and_fix_process &quot;BProcess&quot; &quot;bprocesslist&quot; &quot;a887eedb72472f2ad152e5e91a0727db&quot;</code>: 同上，检查名为 “BProcess” 的进程是否正常运行，使用文件 “bprocesslist” 作为进程ID列表文件，并传递预期的MD5值。</p></li></ol><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>Shell脚本中的函数不需要定义参数，如果用到参数直接是用 <code>$1</code>、 <code>$2</code>、 <code>$3</code>……依次来代替就可以</li><li>Shell脚本函数中使用 $1 作为参数，并不会与内部命令的 $1 冲突，因为Shell在不同的上下文中，变量的作用域是不同的</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/132245367" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>看我ge，看我nai，看我jiu，玩的开心吗？</p><p>2023-8-15 22:41:32</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;说实话，这是我第一次用Shell来定义函数，之前也写了很多shell脚本，但是体量都不大，所以基本上是按照需求罗列了多个命令来实现的，这次也是一样，但是我发现重复的地方太多了，所以还是要拿出看见本事“抽象函数”来解放我懒惰的手指&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/tags/linux/"/>
    
      <category term="运维" scheme="http://AlbertGithubHome.github.io/blog/tags/%E8%BF%90%E7%BB%B4/"/>
    
      <category term="服务器" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
      <category term="Shell函数" scheme="http://AlbertGithubHome.github.io/blog/tags/Shell%E5%87%BD%E6%95%B0/"/>
    
  </entry>
  
  <entry>
    <title>Ubuntu删除大量磁盘文件时，内存cache降free升而创造的漂亮曲线</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/08/15/Ubuntu%E5%88%A0%E9%99%A4%E5%A4%A7%E9%87%8F%E7%A3%81%E7%9B%98%E6%96%87%E4%BB%B6%E6%97%B6%EF%BC%8C%E5%86%85%E5%AD%98cache%E9%99%8Dfree%E5%8D%87%E8%80%8C%E5%88%9B%E9%80%A0%E7%9A%84%E6%BC%82%E4%BA%AE%E6%9B%B2%E7%BA%BF/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/08/15/Ubuntu删除大量磁盘文件时，内存cache降free升而创造的漂亮曲线/</id>
    <published>2023-08-15T14:44:05.000Z</published>
    <updated>2023-09-03T12:52:02.083Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近在做压力测试，对磁盘、CPU、内存等指标比较敏感，因为产生了大量的日志文件需要定期删除，于是写了个定时清理磁盘的脚本，今天早上发现内存曲线居然产生了一个漂亮的图形，不知道你们想到了什么？看到这曲线我首先想到的是《编程之美》中那个“让CPU占用率曲线听你指挥” 的面试题，是不是得加一个想内存使用率曲线听你指挥啊？</p><a id="more"></a><h1 id="图形本尊"><a href="#图形本尊" class="headerlink" title="图形本尊"></a>图形本尊</h1><p>我所说的漂亮曲线入下图所示，是不是有一种对称美？</p><p><img src="https://img-blog.csdnimg.cn/f59c901aa1e945b780072ebc4cef60aa.png" alt="memory"></p><p>搭配这条曲线的还有当时的磁盘占用曲线，两幅图对比基本可以确定这两个指标是相关的</p><p><img src="https://img-blog.csdnimg.cn/a1e60162702149a482c53aeb75024973.png" alt="disk"></p><h1 id="产生的原因"><a href="#产生的原因" class="headerlink" title="产生的原因"></a>产生的原因</h1><p>关于实际的原因我不能给出准确的解释，所以跑去问了GPT，它的回答内容如下，如果有不同意的小伙伴欢迎来反驳：</p><blockquote><p>在Ubuntu或其他Linux操作系统中，当你删除大量磁盘文件时，可能会看到内存缓存（cache）降低而”free”（可用内存）升高的情况。这是由于Linux的内存管理机制导致的，这个机制是为了最大程度地提高系统性能。</p><p>当你删除文件时，内核会将这些文件从文件系统中删除，但为了提高文件访问的效率，已经读取的数据可能会被保留在内存的缓存中。这样，如果在不久的将来需要访问相同的文件，可以直接从内存缓存中获取，而不必从磁盘读取。这个缓存被称为”page cache”，它占用一部分内存。</p><p>当你删除大量文件时，这些文件所占用的内存缓存将被释放，以便更好地利用内存来满足其他正在运行的程序的需求。因此，内存缓存的降低是一个正常现象。</p><p>同时，”free”命令在Linux中显示的”free”内存指的是没有被使用的内存，包括未被分配的内存和被内核回收的缓存。因此，当你删除大量文件时，已释放的缓存会被计算在”free”内存中，导致”free”内存升高。</p><p>这种行为是Linux内存管理的一部分，它旨在在系统性能和内存使用之间找到平衡。当系统需要更多内存时，它可以释放缓存来满足正在运行的程序的需求。所以，不要担心这种情况，它是Linux操作系统的正常行为。</p></blockquote><h1 id="补一个长周期对比图"><a href="#补一个长周期对比图" class="headerlink" title="补一个长周期对比图"></a>补一个长周期对比图</h1><p><img src="https://img-blog.csdnimg.cn/253f2b6dd2454943b24016c08591d5ac.png" alt="mem-disk"></p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>想要控制好计算机资源产生自定义曲线，控制好“消耗”就可以了，但是如何控制是门学问，需要考虑的各种相关因素</li><li>为了提高文件访问的效率，已经读取的数据可能会被保留在内存的缓存中，其被称为”page cache”，便于下次访问直接从内存缓存中获取</li><li>当删除大量文件时，已释放的缓存会被计算在”free”内存中，导致”free”内存升高</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/132240499" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>风急天高猿啸哀，渚清沙白鸟飞回。无边落木萧萧下，不尽长江滚滚来~</p><p>2023-8-12 10:41:18</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;最近在做压力测试，对磁盘、CPU、内存等指标比较敏感，因为产生了大量的日志文件需要定期删除，于是写了个定时清理磁盘的脚本，今天早上发现内存曲线居然产生了一个漂亮的图形，不知道你们想到了什么？看到这曲线我首先想到的是《编程之美》中那个“让CPU占用率曲线听你指挥” 的面试题，是不是得加一个想内存使用率曲线听你指挥啊？&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="Ubuntu" scheme="http://AlbertGithubHome.github.io/blog/tags/Ubuntu/"/>
    
      <category term="DevOps" scheme="http://AlbertGithubHome.github.io/blog/tags/DevOps/"/>
    
      <category term="服务器性能调优" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98/"/>
    
  </entry>
  
  <entry>
    <title>常用游戏运营指标DAU、LTV及参考范围</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/08/11/%E5%B8%B8%E7%94%A8%E6%B8%B8%E6%88%8F%E8%BF%90%E8%90%A5%E6%8C%87%E6%A0%87DAU%E3%80%81LTV%E5%8F%8A%E5%8F%82%E8%80%83%E8%8C%83%E5%9B%B4/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/08/11/常用游戏运营指标DAU、LTV及参考范围/</id>
    <published>2023-08-11T14:28:52.000Z</published>
    <updated>2023-09-03T12:49:58.168Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>作为游戏人免不了听到 <code>DAU</code> 、<code>UP值</code>、<code>留存</code> 等名词，并且有些名词听起来还很像，特别是一款上线的游戏，这些游戏运营指标是衡量游戏业务绩效和用户参与度的重要数据，想做一个合格的游戏人得花点时间了解一下，接下来会总结一些常见的游戏运营指标及其简称。</p><a id="more"></a><h1 id="运营指标"><a href="#运营指标" class="headerlink" title="运营指标"></a>运营指标</h1><ul><li><strong>DAU</strong> - 日活跃用户 (Daily Active Users)：每日登录游戏的独立用户数量</li><li><strong>MAU</strong> - 月活跃用户 (Monthly Active Users)：每月登录游戏的独立用户数量</li><li><strong>ARPU</strong> - 平均收入每用户 (Average Revenue Per User)：平均每个用户带来的收入</li><li><strong>ARPDAU</strong> - 每日平均收入每用户 (Average Revenue Per Daily Active User)：每日平均每个活跃用户带来的收入</li><li><strong>ARPPU</strong> - 平均付费用户收入 (Average Revenue Per Paying User)：平均每个付费用户带来的收入</li><li><strong>Retention Rate</strong> - 留存率：测量特定时间段内用户的保留情况，通常以百分比表示</li><li><strong>Churn Rate</strong> - 流失率：衡量用户流失的速度，通常以月度或年度为周期</li><li><strong>Conversion Rate</strong> - 转化率：衡量用户从免费用户转化为付费用户的比率</li><li><strong>LTV</strong> - 用户生命周期价值 (Lifetime Value)：预测一个用户在其使用周期内可能带来的总收入</li><li><strong>CAC</strong> - 用户获取成本 (Customer Acquisition Cost)：获取一个新用户所花费的成本</li><li><strong>ROI</strong> - 投资回报率 (Return on Investment)：衡量游戏营销和推广活动的效果</li><li><strong>ARPPDAU</strong> - 每日平均收入每付费用户 (Average Revenue Per Paying Daily Active User)：每日平均每个付费活跃用户带来的收入。</li><li><strong>ACPU</strong> - 平均成本每用户 (Average Cost Per User)：获取每个用户的平均成本</li><li><strong>Conversion Funnel</strong> - 转化漏斗：衡量用户从首次接触游戏到最终付费的整个过程</li><li><strong>UGC</strong> - 用户生成内容 (User-Generated Content)：用户创造的游戏内容，如自定义角色、地图等，对游戏社区的活跃度有影响</li></ul><p>这些指标可以帮助游戏开发者和运营者了解游戏的商业表现和用户行为，从而优化游戏的设计和运营策略。</p><h1 id="指标范围参考值"><a href="#指标范围参考值" class="headerlink" title="指标范围参考值"></a>指标范围参考值</h1><p>知道了有上述的运营指标，但是一款合格的游戏上述指标应该达到多少呢？</p><p>其实每款游戏的合格水平可能会因多种因素而有所不同，包括游戏类型、目标市场、营销策略以及开发成本等，并不是固定的标准，实际情况会受到众多因素的影响，包括游戏的质量、竞争情况、玩家喜好以及运营策略等，下面进给出一些参考值。</p><ul><li><strong>DAU</strong> - 日活跃用户：数百到数千，具体取决于游戏的类型和受众规模</li><li><strong>MAU</strong> - 月活跃用户：数千到数万，取决于市场规模和竞争状况</li><li><strong>ARPU</strong> - 平均收入每用户：几美元到十几美元，视付费玩家比例而定</li><li><strong>ARPDAU</strong> - 每日平均收入每用户：几分到几毛美元，视玩家付费情况而定</li><li><strong>ARPPU</strong> - 平均付费用户收入：十美元到数十美元，取决于游戏内购的设计和价值</li><li><strong>Retention Rate</strong> - 留存率：20%-50% 之间，高于 50% 为很好的表现。</li><li><strong>Churn Rate</strong> - 流失率：低于 10% 为很好的表现。</li><li><strong>Conversion Rate</strong> - 转化率：2%-5% 之间，高于 5% 为不错的表现。</li><li><strong>LTV</strong> - 用户生命周期价值：数十美元到数百美元，取决于玩家的长期付费情况。</li><li><strong>CAC</strong> - 用户获取成本：通常 LTV 的 1/3 到 1/2，以确保收益能覆盖用户获取成本。</li></ul><p>这些值仅供参考，并不适用于所有情况。游戏开发者和运营者需要根据自己的情况制定合适的目标，并随着游戏的运营进行不断优化。同时，密切关注竞争对手的表现也是非常重要的。</p><h1 id="留存指标的意义"><a href="#留存指标的意义" class="headerlink" title="留存指标的意义"></a>留存指标的意义</h1><p>留存率是衡量用户参与度和游戏粘性的重要指标之一。它衡量了一段时间内用户的持续参与情况，通常以百分比表示。针对回合制卡牌游戏，留存率的及格线会受到多种因素的影响，包括游戏的类型、受众群体、市场竞争、游戏质量、运营策略等等</p><ul><li><p><strong>次日留存率</strong>：在 30% - 40% 以上被视为相当不错的表现。这意味着有相当比例的玩家在第一次体验游戏后仍然对游戏感兴趣，并愿意在第二天继续游戏。</p></li><li><p><strong>3日留存率</strong>：通常在 20% - 40% 之间被视为良好的表现。这表示在游戏初始安装后的头三天内，有相当比例的玩家愿意继续登录游戏。</p></li><li><strong>7日留存率</strong>：较高的及格线通常在 15% - 30% 左右。相比于3日留存，7日留存更能体现游戏对玩家的长期吸引力。</li><li><strong>月留存率</strong>：这个指标较难界定“及格线”，但在 10% - 20% 以上的范围内被视为不错的表现。月留存更考验游戏的长期吸引力和社区活跃度。</li></ul><p>需要注意的是，这些及格线可能会根据不同的情况有所变化。例如，对于新发布的游戏，初始留存率可能会较高，但随着时间推移，可能会逐渐下降。此外，针对特定类型的玩家群体，留存率也可能有所不同。</p><p>这三个留存率指标都有助于了解游戏的玩家保持情况，但值得注意的是，不同类型的游戏可能会有不同的留存率标准，而且这些指标可能会受到市场、竞争、游戏类型以及运营策略的影响。提高留存率是游戏开发者和运营者追求的目标，因为更高的留存率通常意味着更好的长期经营和用户忠诚度。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li><strong>DAU</strong> - 日活跃用户 (Daily Active Users)：每日登录游戏的独立用户数量</li><li><strong>ARPU</strong> - 平均收入每用户 (Average Revenue Per User)：平均每个用户带来的收入</li><li><strong>LTV</strong> - 用户生命周期价值 (Lifetime Value)：预测一个用户在其使用周期内可能带来的总收入</li><li><strong>Retention Rate</strong> - 留存率：测量特定时间段内用户的保留情况，通常以百分比表示</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/132219946" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>挥手自兹去，萧萧班马鸣~</p><p>2023-8-10 22:34:28</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;作为游戏人免不了听到 &lt;code&gt;DAU&lt;/code&gt; 、&lt;code&gt;UP值&lt;/code&gt;、&lt;code&gt;留存&lt;/code&gt; 等名词，并且有些名词听起来还很像，特别是一款上线的游戏，这些游戏运营指标是衡量游戏业务绩效和用户参与度的重要数据，想做一个合格的游戏人得花点时间了解一下，接下来会总结一些常见的游戏运营指标及其简称。&lt;/p&gt;
    
    </summary>
    
      <category term="Game" scheme="http://AlbertGithubHome.github.io/blog/categories/Game/"/>
    
    
      <category term="游戏" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%B8%B8%E6%88%8F/"/>
    
      <category term="大数据" scheme="http://AlbertGithubHome.github.io/blog/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    
      <category term="运行指标" scheme="http://AlbertGithubHome.github.io/blog/tags/%E8%BF%90%E8%A1%8C%E6%8C%87%E6%A0%87/"/>
    
      <category term="DAU" scheme="http://AlbertGithubHome.github.io/blog/tags/DAU/"/>
    
      <category term="LTV" scheme="http://AlbertGithubHome.github.io/blog/tags/LTV/"/>
    
  </entry>
  
  <entry>
    <title>在Ubuntu系统下修改limits.conf不生效</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/08/10/%E5%9C%A8Ubuntu%E7%B3%BB%E7%BB%9F%E4%B8%8B%E4%BF%AE%E6%94%B9limits-conf%E4%B8%8D%E7%94%9F%E6%95%88/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/08/10/在Ubuntu系统下修改limits-conf不生效/</id>
    <published>2023-08-10T14:37:32.000Z</published>
    <updated>2023-09-03T12:47:12.258Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近遇到的一个问题，在Ubuntu系统下修改<code>/etc/security/limits.conf</code>不生效，查了多种资料都说不用重启，但是我改完就是不生效，多次尝试之后发现Ubuntu系统有毒。</p><a id="more"></a><h1 id="尝试过程"><a href="#尝试过程" class="headerlink" title="尝试过程"></a>尝试过程</h1><p>通过 <code>ulimit -n</code> 命令可以查询到当前系统单个进程允许打开的文件描述符：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">ulimit</span> -n</span><br><span class="line">1024</span><br></pre></td></tr></table></figure><p>才1024个，放到游戏服务器上显然不太够用，于是使用命令 <code>ulimit -n 65535</code> 手动调大了，但是这种方式只能对当前登录session有效，下次新的连接再启动进程数值又回去了，想要一劳永逸，就得修改<code>/etc/security/limits.conf</code>文件，打开文件加入下面的内容：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">* soft nofile 65535</span><br><span class="line">* hard nofile 65535</span><br></pre></td></tr></table></figure><p>关闭文件，退出重新登录，运行 <code>ulimit -n</code> 命令发现还是1024，奇怪！</p><p>后续有尝试了网络上常常提到的 <code>pam_limits</code> 配置，按步骤修改后不好使，查找 <code>/etc/systemd/user.conf</code> 文件配置也无法对应，最后查到一种解决方式，将 limits.conf 文件中的 <code>*</code> 写成具体的用户名就好了</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root soft nofile 65535</span><br><span class="line">root hard nofile 65535</span><br></pre></td></tr></table></figure><p>之前我在CentOS系统上一直是用星号的，每想到在Ubuntu系统上被坑了一次</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li><code>ulimit -n 65535</code> 命令可以直接将单个进程打开的文件描述符限制扩大到65535</li><li>修改 <code>/etc/security/limits.conf</code> 文件不需要重启系统，退出ssh重新登录记录</li><li>在CentOS中修改 limits.conf 文件中的用户名可以用 <code>*</code>，但是在Ubuntu中需要写具体用户名</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/132033812" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>高位易跌倒，高位易昏头，面对巨大的诱惑，想要做到不被拉下水是在太难了~</p><p>2023-8-10 22:34:28</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;最近遇到的一个问题，在Ubuntu系统下修改&lt;code&gt;/etc/security/limits.conf&lt;/code&gt;不生效，查了多种资料都说不用重启，但是我改完就是不生效，多次尝试之后发现Ubuntu系统有毒。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/tags/linux/"/>
    
      <category term="运维" scheme="http://AlbertGithubHome.github.io/blog/tags/%E8%BF%90%E7%BB%B4/"/>
    
      <category term="ubuntu" scheme="http://AlbertGithubHome.github.io/blog/tags/ubuntu/"/>
    
      <category term="root" scheme="http://AlbertGithubHome.github.io/blog/tags/root/"/>
    
      <category term="ulimit" scheme="http://AlbertGithubHome.github.io/blog/tags/ulimit/"/>
    
  </entry>
  
  <entry>
    <title>linux环境下运行脚本时常用＞/dev/null 2＞&amp;1 &amp;这一串的作用</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/08/01/linux%E7%8E%AF%E5%A2%83%E4%B8%8B%E8%BF%90%E8%A1%8C%E8%84%9A%E6%9C%AC%E6%97%B6%E5%B8%B8%E7%94%A8%EF%BC%9E-dev-null-2%EF%BC%9E-1-%E8%BF%99%E4%B8%80%E4%B8%B2%E7%9A%84%E4%BD%9C%E7%94%A8/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/08/01/linux环境下运行脚本时常用＞-dev-null-2＞-1-这一串的作用/</id>
    <published>2023-07-31T17:56:49.000Z</published>
    <updated>2023-09-03T12:42:44.440Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>大雨哗哗哗，叫车无人接，把最近用到的命令总结一下，方便日后的拿来主义。</p><a id="more"></a><h1 id="gt-dev-null-2-gt-amp-1"><a href="#gt-dev-null-2-gt-amp-1" class="headerlink" title="&gt;/dev/null 2&gt;&amp;1"></a>&gt;/dev/null 2&gt;&amp;1</h1><p><code>&gt;/dev/null 2&gt;&amp;1 &amp;</code> 是一个常见的 Shell 命令行用法，用于在 Linux 或类 Unix 系统上运行一个程序并将其输出（标准输出和标准错误输出）重定向到 <code>/dev/null</code>，同时将程序在后台（background）运行。</p><p>接下来可以逐步解释下各部分的作用：</p><ul><li><p><code>&gt;</code>：重定向符号，用于将输出重定向到指定的文件或设备。</p></li><li><p><code>/dev/null</code>：在 Unix 系统上，<code>/dev/null</code> 是一个特殊的设备文件，它会将所有写入它的内容都丢弃，相当于一个空黑洞。将输出重定向到 <code>/dev/null</code> 就意味着将输出丢弃，不会显示在终端或记录到文件中。</p></li><li><p><code>2&gt;</code>：重定向标准错误输出的符号。<code>2</code> 表示标准错误流，也称为文件描述符 2。</p></li><li><p><code>&amp;1</code>：将标准错误输出重定向到标准输出的符号。<code>&amp;1</code> 表示文件描述符 1，而标准输出的文件描述符也是 1。这样，就将标准错误输出和标准输出合并到一起，都重定向到了 <code>/dev/null</code>。</p></li><li><p><code>&amp;</code>：在命令行中，<code>&amp;</code> 符号用于将命令置于后台运行，使得当前终端可以继续输入其他命令，而不需要等待前台命令的完成。</p></li></ul><p>综合起来，<code>&gt;/dev/null 2&gt;&amp;1 &amp;</code> 的作用是将某个程序在后台运行，并将其标准输出和标准错误输出都丢弃，不在终端中显示或记录。这样做常常用于运行不需要显示输出的程序或脚本，并且让它在后台运行，不占用终端的输入输出。</p><h1 id="去除文件夹下所有文本文件的行末空格"><a href="#去除文件夹下所有文本文件的行末空格" class="headerlink" title="去除文件夹下所有文本文件的行末空格"></a>去除文件夹下所有文本文件的行末空格</h1><p>想要达到这个目的有多种实现方式，比如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find . -<span class="built_in">type</span> f -<span class="built_in">exec</span> egrep -l <span class="string">" +$"</span> &#123;&#125; \; | grep -v xxx | xargs sed -i <span class="string">'s/[ ]*$//g'</span></span><br></pre></td></tr></table></figure><p>再比如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find xxx -<span class="built_in">type</span> f -name <span class="string">"*.txt"</span> -<span class="built_in">exec</span> sed -i <span class="string">'s/[ \t]*$//'</span> &#123;&#125; \;</span><br></pre></td></tr></table></figure><p>主要部分的含义：</p><ul><li><p><code>-type f</code>：<code>-type f</code> 参数用于限制 <code>find</code> 命令只查找普通文件（非目录、符号链接等）。</p></li><li><p><code>-name &quot;*.txt&quot;</code>：<code>-name &quot;*.txt&quot;</code> 参数用于匹配文件名，这里匹配以 <code>.txt</code> 结尾的文件。您可以根据实际需要修改为其他文件扩展名。</p></li><li><p><code>-exec</code>：<code>-exec</code> 参数用于对找到的每个文件执行后面的命令。</p></li><li><p><code>sed -i &#39;s/[ \t]*$//&#39; {}</code>：<code>sed</code> 命令用于进行文本替换。<code>-i</code> 参数表示直接在原文件上进行修改（in-place），而不是在终端输出。<code>s/[ \t]*$//</code> 是一个替换表达式，用于匹配行末的空格（包括空格和制表符），将其替换为空。</p></li><li><p><code>{}</code>：<code>find</code> 命令中的占位符，代表找到的每个文件名。</p></li><li><p><code>\;</code>：<code>find</code> 命令中的结束符，表示 <code>-exec</code> 参数的结束。</p></li></ul><p>还可以利用管道来实现，使用管道 (Pipeline) 去掉一个文件夹下所有文本文件行末的空格，可以结合 <code>find</code> 命令和 <code>sed</code> 命令来完成。管道允许将一个命令的输出作为另一个命令的输入，以实现更复杂的文本处理，如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find xxx -<span class="built_in">type</span> f -name <span class="string">"*.txt"</span> -print0 | xargs -0 sed -i <span class="string">'s/[ \t]*$//'</span></span><br></pre></td></tr></table></figure><p>各部分的含义：</p><ul><li><p><code>find xxx -type f -name &quot;*.txt&quot; -print0</code>：<code>find</code> 命令查找指定目录 <code>xxx</code> 下的所有以 <code>.txt</code> 结尾的文本文件，并使用 <code>-print0</code> 参数将文件名以空字符作为分隔符打印出来。这是为了处理文件名中可能包含空格或特殊字符的情况。</p></li><li><p><code>|</code>：管道符号，将 <code>find</code> 命令的输出传递给下一个命令。</p></li><li><p><code>xargs -0</code>：<code>xargs</code> 命令用于接收管道的输入，并将其作为参数传递给后面的命令。<code>-0</code> 参数表示使用空字符作为输入分隔符，与 <code>find</code> 命令的 <code>-print0</code> 参数相对应。</p></li><li><p><code>sed -i &#39;s/[ \t]*$//&#39;</code>：<code>sed</code> 命令用于进行文本替换，同样使用 <code>-i</code> 参数进行直接修改。<code>s/[ \t]*$//</code> 是一个替换表达式，用于去掉行末的空格。</p></li></ul><p>与之前的命令相比，这个命令通过管道一次性处理了多个文件，效率可能会更高一些。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>将某个程序在后台运行，并将其标准输出和标准错误输出都丢弃，可以在运行命令后加 <code>&gt;/dev/null 2&gt;&amp;1</code></li><li>去除文件行末空格 <code>find . -type f -exec egrep -l &quot; +$&quot; {} \; | grep -v xxx | xargs sed -i &#39;s/[ ]*$//g&#39;</code></li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/131861112" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>机会很重要，选择也很重要，在机会面前做出选择同样很重要，不要过分夸大未选择的那条路，很多时候即使重新选择一次，大部分人的道路不会发生改变，已经定型了…</p><p>我也不知道是熬到了司机来，还是把前边排队的人熬走了，现在还有3人排队~</p><p>2023-8-1 01:47:54</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;大雨哗哗哗，叫车无人接，把最近用到的命令总结一下，方便日后的拿来主义。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/tags/linux/"/>
    
      <category term="运维" scheme="http://AlbertGithubHome.github.io/blog/tags/%E8%BF%90%E7%BB%B4/"/>
    
      <category term="服务器" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
      <category term="dev/null" scheme="http://AlbertGithubHome.github.io/blog/tags/dev-null/"/>
    
  </entry>
  
  <entry>
    <title>之前好好的distcc突然报错distcc[18595] (dcc_pump_sendfile) ERROR: sendfile failed: Broken pipe怎么办</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/07/21/%E4%B9%8B%E5%89%8D%E5%A5%BD%E5%A5%BD%E7%9A%84distcc%E7%AA%81%E7%84%B6%E6%8A%A5%E9%94%99distcc-18595-dcc-pump-sendfile-ERROR-sendfile-failed-Broken-pipe%E6%80%8E%E4%B9%88%E5%8A%9E/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/07/21/之前好好的distcc突然报错distcc-18595-dcc-pump-sendfile-ERROR-sendfile-failed-Broken-pipe怎么办/</id>
    <published>2023-07-21T14:39:23.000Z</published>
    <updated>2023-09-03T12:40:07.046Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p><code>distcc</code> 是一个用于分布式编译的工具，它可以将编译任务分发到多台计算机上，从而加快代码编译的速度。<code>distcc</code> 允许多台计算机共同参与编译过程，利用分布式系统的优势来并行处理编译任务，从而提高编译速度和性能。</p><a id="more"></a><p>在传统的编译过程中，代码被发送到一台计算机上，然后由该计算机上的编译器进行编译。这种方式在大型项目中可能会消耗大量时间，特别是当项目变得复杂并且代码量增加时，编译时间会显著增加。</p><p><code>distcc</code> 解决了这个问题，它通过在局域网内或远程服务器上设置 <code>distcc</code> 客户端和 <code>distccd</code> 服务器来实现分布式编译。<code>distcc</code> 客户端将编译任务发送到 <code>distccd</code> 服务器，而服务器上的 <code>distccd</code> 守护进程将任务分发给多个可用的编译器进行并行编译。一旦编译完成，编译结果将发送回到 <code>distcc</code> 客户端，然后再进行链接等后续处理。</p><p>通过 <code>distcc</code> 的使用，编译过程可以更快地完成，节省了开发人员的时间，并且利用了局域网或网络上的多台计算机的计算资源，提高了编译效率。</p><p>distcc支持多种编程语言，包括C，C ++，Objective-C和Fortran。它是一个开源软件，可以在Linux，Unix和Windows等平台上运行。</p><h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><p>前面主要简单的叙述了什么是<code>distcc</code>，我遇到的问题就是前几天用的好好的<code>distcc</code>，最近总是报出以下错误：</p><blockquote><p>distcc[18595] (dcc_pump_sendfile) ERROR: sendfile failed: Broken pipe<br>distcc[18595] (dcc_readx) ERROR: unexpected eof on fd6<br>distcc[18595] (dcc_r_token_int) ERROR: read failed while waiting for token “DONE”<br>distcc[18595] (dcc_r_result_header) ERROR: server provided no answer. Is the server configured to allow access from your IP address? Is the server performing authentication and your client isn’t? Does the server have the compiler installed? Is the server configured to access the compiler?<br>distcc[18595] Warning: failed to distribute /home/demo/workspace/x_server/gameserver/battle/effect/addbuffroundeffectexecutormodule.cpp to 10.12.7.181, running locally instead</p></blockquote><p>先是Google一番没找到解决方案，又去问了ChatGPT，给出的答案偏向于权限问题，这就很奇怪，我之前用的好好的，为啥突然会出现权限问题，他可能是错误描述的影响：</p><blockquote><p>ERROR: server provided no answer. Is the server configured to allow access from your IP address? Is the server performing authentication and your client isn’t? Does the server have the compiler installed? Is the server configured to access the compiler?</p></blockquote><h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><p>一开始我也是朝着配置和权限来努力的，可是怎样重启服务或是修改配置都不好使，查了防火墙也没问题，telnet 3262端口也是通的，最终我把目光定位到了<code>sendfile failed: Broken pipe</code> 这句话，不会是磁盘满了吧？<code>df</code>一查还真是，唉，折腾了好久~</p><h1 id="exec-command-和-exec-command-区别"><a href="#exec-command-和-exec-command-区别" class="headerlink" title="-exec command ; 和-exec command {} +区别"></a>-exec command ; 和-exec command {} +区别</h1><p>这篇内容有点少，补充一个我今天刚学到的知识点吧~</p><p>在 <code>find</code> 命令中，<code>-exec command ;</code> 和 <code>-exec command {} +</code> 是两种不同的方式来执行指定的命令。</p><h2 id="exec-command"><a href="#exec-command" class="headerlink" title="-exec command ;"></a>-exec command ;</h2><p>   这种方式将对 <code>find</code> 命令找到的每个文件依次执行一次指定的命令。每找到一个匹配的文件，都会执行一次该命令。例如：</p>   <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find /path/to/search -<span class="built_in">type</span> f -name <span class="string">"*.txt"</span> -<span class="built_in">exec</span> <span class="built_in">echo</span> &#123;&#125; \;</span><br></pre></td></tr></table></figure><p>   上述命令会在 <code>/path/to/search</code> 目录及其子目录中搜索所有扩展名为 <code>.txt</code> 的文件，并依次输出每个文件的名称。</p><h2 id="exec-command-1"><a href="#exec-command-1" class="headerlink" title="-exec command {} +"></a>-exec command {} +</h2><p>   这种方式也会执行指定的命令，但是它会将多个找到的文件一起传递给命令进行一次执行。这种方式通常比使用 <code>;</code> 更高效，因为它可以将多个文件组合在一起，减少了命令的调用次数。例如：</p>   <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">find /path/to/search -<span class="built_in">type</span> f -name <span class="string">"*.txt"</span> -<span class="built_in">exec</span> cat &#123;&#125; +</span><br></pre></td></tr></table></figure><p>   上述命令会在 <code>/path/to/search</code> 目录及其子目录中搜索所有扩展名为 <code>.txt</code> 的文件，并将它们一起传递给 <code>cat</code> 命令进行合并输出。</p><p>如果只需要针对每个文件执行一次命令，可以使用 <code>-exec command ;</code>。如果想要将多个文件组合在一起一次性执行命令，可以使用 <code>-exec command {} +</code>。选择哪种方式取决于具体的需求和效率考虑。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>当<code>distcc</code>突然不好使并提示<code>sendfile failed: Broken pipe</code>的时候去看看服务器磁盘是不是满了</li><li>使用 <code>-exec command ;</code> 将对每个匹配的文件分别执行一次命令。</li><li>使用 <code>-exec command {} +</code> 将多个匹配的文件一起传递给命令执行。</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/131839789" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>今朝有今朝醉不是一种豁达，或许是一种无可奈何吧~</p><p>2023-7-21 22:31:00</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;&lt;code&gt;distcc&lt;/code&gt; 是一个用于分布式编译的工具，它可以将编译任务分发到多台计算机上，从而加快代码编译的速度。&lt;code&gt;distcc&lt;/code&gt; 允许多台计算机共同参与编译过程，利用分布式系统的优势来并行处理编译任务，从而提高编译速度和性能。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="运维" scheme="http://AlbertGithubHome.github.io/blog/tags/%E8%BF%90%E7%BB%B4/"/>
    
      <category term="distcc" scheme="http://AlbertGithubHome.github.io/blog/tags/distcc/"/>
    
      <category term="c++" scheme="http://AlbertGithubHome.github.io/blog/tags/c/"/>
    
      <category term="分布式编译" scheme="http://AlbertGithubHome.github.io/blog/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E7%BC%96%E8%AF%91/"/>
    
      <category term="编译加速" scheme="http://AlbertGithubHome.github.io/blog/tags/%E7%BC%96%E8%AF%91%E5%8A%A0%E9%80%9F/"/>
    
  </entry>
  
  <entry>
    <title>linux环境下rsync命令中两个目录结尾的斜杠‘/‘到底有什么作用</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/07/20/linux%E7%8E%AF%E5%A2%83%E4%B8%8Brsync%E5%91%BD%E4%BB%A4%E4%B8%AD%E4%B8%A4%E4%B8%AA%E7%9B%AE%E5%BD%95%E7%BB%93%E5%B0%BE%E7%9A%84%E6%96%9C%E6%9D%A0%E2%80%98-%E2%80%98%E5%88%B0%E5%BA%95%E6%9C%89%E4%BB%80%E4%B9%88%E4%BD%9C%E7%94%A8/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/07/20/linux环境下rsync命令中两个目录结尾的斜杠‘-‘到底有什么作用/</id>
    <published>2023-07-20T14:40:21.000Z</published>
    <updated>2023-09-03T12:38:57.311Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>每次使用文件拷贝 <code>cp</code> 命令或者同步 <code>rsync</code> 命令时都会有些疑惑，不知道路径末尾的 <code>/</code>需不需要加，隐约感觉到有些不同，但具体的区别却不太清楚，需要实际操作一次才能知道了，下面就来试一试。</p><a id="more"></a><p>rsync 命令的基础格式为 <code>rsync [OPTION...] SRC... [DEST]</code>，可用于本地文件的同步，如果想同步远程文件也可以，将 <code>SRC</code> 或者 <code>DEST</code> 改成带有IP的远程地址就可以了，但是最多支持一个远端地址，不能两个目录都写成remote格式，其实这个命令之前也用过好几次了，比如 <a href="https://blog.csdn.net/albertsh/article/details/129656549" target="_blank" rel="noopener">《linux环境下利用rsync+find实现同步指定时间段文件》</a>，这次来看看结尾的 <code>/</code> 到底有什么影响。</p><h1 id="准备测试环境"><a href="#准备测试环境" class="headerlink" title="准备测试环境"></a>准备测试环境</h1><p>测试环境很简单，只要准备源目录和目标目录就可以了，为了测试各种情况，我需要创建一个比较丰富的目录结构，需要测试的情况有原目录A结尾有无 <code>/</code>，目标目录B结尾有无 <code>/</code>，目标目录B中有无同名A文件夹，A文件夹是否为空，目标目录B中有无同名A文件，目标目录不存在，各种情况组合应该是 2 * 2 * 5 = 20种，这不是全部的情况，但已经覆盖大部分想要测试的问题了，因为使用 rsync 通常是同步目录，暂时不考虑源和目标是文件的情况。</p><p>测试目录结构如下：</p><p><img src="https://img-blog.csdnimg.cn/ac2a260984eb4816a958bf87df4d2f9f.png" alt="origin"><br>事先说明，以下的每项测试都是在这个原始的基础上进行的，每项测试完成会将目录还原，用以比较不同命令参数下结果的区别，测试选项统一使用 <code>-a --delete</code>。</p><h1 id="rsync测试"><a href="#rsync测试" class="headerlink" title="rsync测试"></a>rsync测试</h1><h2 id="测试过程"><a href="#测试过程" class="headerlink" title="测试过程"></a>测试过程</h2><p>分小组进行，便于对比</p><ol><li><p><strong>源目录和目标目录结尾都没斜杠 SRC DEST</strong><br><img src="https://img-blog.csdnimg.cn/daf8c1a8a0ff476fae126e9319292320.png" alt="1-1"><br><img src="https://img-blog.csdnimg.cn/b59b5ece33284f42a14a40309108fd3e.png" alt="1-2"><br><img src="https://img-blog.csdnimg.cn/e7d3710b5afd42ddb46c2adab53866d1.png" alt="1-3"><br><img src="https://img-blog.csdnimg.cn/a90100680b424f379f745ad7948ea644.png" alt="1-4"><br><img src="https://img-blog.csdnimg.cn/36e33860a5804942b04c8dead3b3c930.png" alt="1-5"><br>这种情况的测试结论为，不论目标目录是否存在（没有就创建），都会将源目录拷贝到目标目录之内形成同名文件夹，如果原来有同名文件或文件夹就覆盖掉，如果不加 <code>--delete</code> 就会合并。</p></li><li><p><strong>源目录结尾有斜杠而目标目录结尾没斜杠 SRC/ DEST</strong><br><img src="https://img-blog.csdnimg.cn/fb582efc6c0f40dab87d156555987d86.png" alt="2-1"><br>为了能更快的看到效果，这次我直接一个命令把5种情况都测试了，结论是如果源目录结尾有斜杠，会将源目录中的内容同步到目标目录之内，覆盖掉目标目录之内原有的内容，如果不加 <code>--delete</code> 就会和目标目录合并。</p></li><li><p><strong>源目录结尾没有斜杠而目标目录结尾有斜杠 SRC DEST/</strong><br><img src="https://img-blog.csdnimg.cn/812544ea19b845a1b7229409361cdd76.png" alt="3-1"><br>还是采用了一个命令测试5种情况的方式，与第一组测试结果一致，都是在目标目录下增加了源目录文件夹，内容保持一致。</p></li><li><p><strong>源目录结尾和目标目录结尾都有斜杠 SRC/ DEST/</strong><br><img src="https://img-blog.csdnimg.cn/bf2837488ba347209b70eeb26a3226a0.png" alt="4-1"><br>测试结果与第二组一致，不再赘述。</p><h2 id="测试结论"><a href="#测试结论" class="headerlink" title="测试结论"></a>测试结论</h2></li></ol><p>在 rsync 中，执行结果只与原始目录结尾有无斜杠有关，如果不传递原始目录（也称为源目录）的末尾斜杠（/），它将尝试在目标目录中创建源目录的副本，而不是将源目录的内容复制到目标目录中。换句话说，如果没有末尾斜杠，rsync 会尝试在目标目录下创建一个与源目录同名的目录，并将源目录的内容复制到这个同名目录中。</p><p>可以简单记忆为，如果原始目录没加末尾斜杠，表示将整个目录拷贝到目标文件夹，如果原始目录加了末尾斜杠，表示将原始目录中的内容拷贝到目标文件夹。</p><h1 id="cp和mv两个命令测试"><a href="#cp和mv两个命令测试" class="headerlink" title="cp和mv两个命令测试"></a>cp和mv两个命令测试</h1><p>看了上面的测试如果你觉得明白了，那么下面的内容可能会让你再次感到混乱，其实每次使用 cp 命令时还不怎么担心，但是使用mv命令时总是害怕把什么东西覆盖了，还是利用上面的目录结构来测试，只是就不贴具体的测试过程了，我把结论写一下。</p><p>cp 和 mv 仅考虑参数均为目录的情况，两个命令中无论是原始目录还是目标目录，结尾是否加斜杠 <code>/</code> 没有影响，都表示目录本身。</p><ul><li>cp 的目标目录不存在，则创建一个与原始目录内容一致的目标文件夹</li><li>cp 的目标目录若存在，则会将原始目录拷贝到目标文件夹下，若在目标文件夹下存在同名目录则合并，若在目标文件夹下存在同名文件，则执行失败，报错 <code>cp: cannot overwrite non-directory &#39;xxx/vvv&#39; with directory &#39;vvv&#39;</code></li><li>mv 的目标目录不存在，则创建一个与原始目录内容一致的目标文件夹，并将原始目录移除</li><li>mv 的目标目录若存在，则将原始目录移动到目标文件夹之下，若在目标文件夹下存在同名空目录则移动成功，若存在同名非空目录则执行失败，报错 <code>mv: cannot move &#39;vvv&#39; to &#39;zzz/vvv&#39;: Directory not empty</code>，若在目标文件夹下存在同名文件，也执行失败，报错 <code>mv: cannot overwrite non-directory &#39;xxx/vvv&#39; with directory &#39;vvv&#39;</code></li></ul><p>所以这样看来mv用于目录时也不会覆盖掉原始的内容，以后可以放心大胆的用了</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li><code>rsync</code> 命令只有原始目录结尾的斜杠能影响结果，没有斜杠整个原始目录拷贝过去，有斜杠只拷贝原始目录里的内容到目标文件夹</li><li><code>cp</code> 和 <code>mv</code> 在目标目录不存在是都能执行成功，cp 是复制一份到形成目标目录，mv是将原始目录移动到目标目录</li><li><code>cp</code> 的目标目录若存在，其中不能包含和原始目录同名的文件，如果文件夹同名会将内容合并</li><li><code>mv</code> 的目标目录若存在，其中不能包含和原始目录同名的文件或非空同名文件夹，如果存在同名空文件夹则会将内容合并</li><li><code>mv</code> 命令操作目录时不会随便覆盖，可以放心使用</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/131528691" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>到底是坚持获得了成功，还是成功之后才能坚持下去，其实要看你怎么定义成功了，不怨我们浮躁，很多先机已经被人家占完了~</p><p>2023-7-20 22:32:42</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;每次使用文件拷贝 &lt;code&gt;cp&lt;/code&gt; 命令或者同步 &lt;code&gt;rsync&lt;/code&gt; 命令时都会有些疑惑，不知道路径末尾的 &lt;code&gt;/&lt;/code&gt;需不需要加，隐约感觉到有些不同，但具体的区别却不太清楚，需要实际操作一次才能知道了，下面就来试一试。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/tags/linux/"/>
    
      <category term="运维" scheme="http://AlbertGithubHome.github.io/blog/tags/%E8%BF%90%E7%BB%B4/"/>
    
      <category term="rsync" scheme="http://AlbertGithubHome.github.io/blog/tags/rsync/"/>
    
      <category term="mv" scheme="http://AlbertGithubHome.github.io/blog/tags/mv/"/>
    
      <category term="cp" scheme="http://AlbertGithubHome.github.io/blog/tags/cp/"/>
    
  </entry>
  
  <entry>
    <title>C++中的 i++ 和 ++i 这两种自增运算不是原子操作</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/07/04/C-%E4%B8%AD%E7%9A%84-i-%E5%92%8C-i-%E8%BF%99%E4%B8%A4%E7%A7%8D%E8%87%AA%E5%A2%9E%E8%BF%90%E7%AE%97%E4%B8%8D%E6%98%AF%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/07/04/C-中的-i-和-i-这两种自增运算不是原子操作/</id>
    <published>2023-07-04T05:31:49.000Z</published>
    <updated>2023-09-03T12:31:53.378Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>C++中的 <code>i++</code> 和 <code>++i</code> 这两种自增运算是不是原子操作，突然被这么一问竟有点不知所措，这么“简单”的操作应该是原子的吧，但是好像有读又有写应该不是原子操作，原子操作就是那种刷一下就能完成的操作，准确来描述就是一个操作不可再分，要完成都完成不能存在中间态，咦？怎么听起来和事务这么像？那么 <code>i++</code> 和 <code>++i</code> 是不是原子操作我们看它是否满足不可再分就行了。</p><a id="more"></a><h1 id="原子操作"><a href="#原子操作" class="headerlink" title="原子操作"></a>原子操作</h1><p>怎么看是否可再分呢？想到一个办法，看一个操作是否可再分，直接看汇编是不是就行了，比如一个赋值语句：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> i = <span class="number">110</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用 x86-64 gcc 13.1编译后生成的汇编：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">main:</span><br><span class="line">        push    rbp</span><br><span class="line">        mov     rbp, rsp</span><br><span class="line">        mov     DWORD PTR [rbp-4], 110</span><br><span class="line">        mov     eax, 0</span><br><span class="line">        pop     rbp</span><br><span class="line">        ret</span><br></pre></td></tr></table></figure><p><code>int i = 110;</code> 被汇编成了 <code>mov     DWORD PTR [rbp-4], 110</code> 看起来是一句，没啥问题，再看 <code>i++</code>:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> i = <span class="number">110</span>;</span><br><span class="line">    i++;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">main:</span><br><span class="line">        push    rbp</span><br><span class="line">        mov     rbp, rsp</span><br><span class="line">        mov     DWORD PTR [rbp-4], 110</span><br><span class="line">        add     DWORD PTR [rbp-4], 1</span><br><span class="line">        mov     eax, 0</span><br><span class="line">        pop     rbp</span><br><span class="line">        ret</span><br></pre></td></tr></table></figure><p><code>i++</code> 也被汇编成了一句 <code>add     DWORD PTR [rbp-4], 1</code>，居然也是一句，那么这是原子的吗？我们换个编译器看看，使用 <code>x64 msvc 19.35</code> 生成的汇编如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">i$ = 0</span><br><span class="line">main    PROC</span><br><span class="line">$LN3:</span><br><span class="line">        sub     rsp, 24</span><br><span class="line">        mov     DWORD PTR i$[rsp], 110                    ; 0000006eH</span><br><span class="line">        mov     eax, DWORD PTR i$[rsp]</span><br><span class="line">        inc     eax</span><br><span class="line">        mov     DWORD PTR i$[rsp], eax</span><br><span class="line">        xor     eax, eax</span><br><span class="line">        add     rsp, 24</span><br><span class="line">        ret     0</span><br><span class="line">main    ENDP</span><br></pre></td></tr></table></figure><p>看吧，这里被翻译成了3句，这肯定不是原子操作了，那返回来看在gcc编译时生成 <code>add     DWORD PTR [rbp-4], 1</code>的这一句，就是原子操作吗？</p><p>准确来表述是这样的 <code>add DWORD PTR [rbp-4], 1</code>这条汇编指令本身是原子的，但是在多线程环境中，对于变量的自增操作需要使用适当的同步机制（如互斥锁、原子类型等）来确保原子性和线程安全性。</p><p>如果在单核机器上，上述不加锁不会有问题，但到了多核机器上，这个不加锁同样会带来意外后果，两个CPU可以同时执行这条指令，但是两个执行以后，却可能出现只自加了一次</p><h1 id="证明-i不是原子操作的例子"><a href="#证明-i不是原子操作的例子" class="headerlink" title="证明++i不是原子操作的例子"></a>证明++i不是原子操作的例子</h1><p>写个简单的例子，两个线程同时执行i++自增操作，看最后的结果是否符合预期：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;thread&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> val = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f1</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; ++i) ++val;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span>* argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> n = <span class="number">100000000</span>;</span><br><span class="line">    <span class="keyword">if</span> (argc &gt; <span class="number">1</span>) n = atoi(argv[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">std</span>::<span class="function">thread <span class="title">t1</span><span class="params">(f1, n)</span></span>;</span><br><span class="line">    <span class="built_in">std</span>::<span class="function">thread <span class="title">t2</span><span class="params">(f1, n)</span></span>;</span><br><span class="line"></span><br><span class="line">    t1.join();</span><br><span class="line">    t2.join();</span><br><span class="line"></span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The final value is ["</span> &lt;&lt; val &lt;&lt; <span class="string">"] for 2 threads running ["</span> &lt;&lt; n &lt;&lt; <span class="string">"] times."</span> &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行结果如下：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ ./iplusplus 1000</span><br><span class="line">The final value is [2000] <span class="keyword">for</span> 2 threads running [1000] <span class="built_in">times</span>.</span><br><span class="line"></span><br><span class="line">$ ./iplusplus 10000</span><br><span class="line">The final value is [20000] <span class="keyword">for</span> 2 threads running [10000] <span class="built_in">times</span>.</span><br><span class="line"></span><br><span class="line">$ ./iplusplus 100000</span><br><span class="line">The final value is [117784] <span class="keyword">for</span> 2 threads running [100000] <span class="built_in">times</span>.</span><br><span class="line"></span><br><span class="line">$ ./iplusplus 1000000</span><br><span class="line">The final value is [1271769] <span class="keyword">for</span> 2 threads running [1000000] <span class="built_in">times</span>.</span><br></pre></td></tr></table></figure><p>从运行结果得知，起初1000次和10000次还没出现竞态条件问题，当次数扩大到100000次时，2个线程最终自增的结果只有117784</p><h1 id="保证原子操作"><a href="#保证原子操作" class="headerlink" title="保证原子操作"></a>保证原子操作</h1><p>还是上面的例子，怎样改成原子操作呢？这时可以利用 <code>std::atomic</code> 模板类，只需将上述例子中的 <code>val</code> 变量修改成 <code>std::atomic&lt;int&gt; val(0);</code> 即可：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;thread&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;atomic&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="built_in">std</span>::atomic&lt;<span class="keyword">int</span>&gt; val(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">f1</span><span class="params">(<span class="keyword">int</span> n)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; ++i) ++val;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span>* argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> n = <span class="number">100000000</span>;</span><br><span class="line">    <span class="keyword">if</span> (argc &gt; <span class="number">1</span>) n = atoi(argv[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">std</span>::<span class="function">thread <span class="title">t1</span><span class="params">(f1, n)</span></span>;</span><br><span class="line">    <span class="built_in">std</span>::<span class="function">thread <span class="title">t2</span><span class="params">(f1, n)</span></span>;</span><br><span class="line"></span><br><span class="line">    t1.join();</span><br><span class="line">    t2.join();</span><br><span class="line"></span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The final value is ["</span> &lt;&lt; val &lt;&lt; <span class="string">"] for 2 threads running ["</span> &lt;&lt; n &lt;&lt; <span class="string">"] times."</span> &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再编译运行试试：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$ ./iplusplus 100</span><br><span class="line">The final value is [200] <span class="keyword">for</span> 2 threads running [100] <span class="built_in">times</span>.</span><br><span class="line"></span><br><span class="line">$ ./iplusplus 10000</span><br><span class="line">The final value is [20000] <span class="keyword">for</span> 2 threads running [10000] <span class="built_in">times</span>.</span><br><span class="line"></span><br><span class="line">$ ./iplusplus 1000000</span><br><span class="line">The final value is [2000000] <span class="keyword">for</span> 2 threads running [1000000] <span class="built_in">times</span>.</span><br><span class="line"></span><br><span class="line">$ ./iplusplus 100000000</span><br><span class="line">The final value is [200000000] <span class="keyword">for</span> 2 threads running [100000000] <span class="built_in">times</span>.</span><br></pre></td></tr></table></figure><p>这样就解决了i++不是原子操作的问题，这里还可以将 <code>++val</code> 写成 <code>val.fetch_add(1)</code> 表示原子加，其实 <code>std::atomic</code> 类实现了 <code>operator++</code> 调用的就是 <code>fetch_add(1)</code>：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">_GLIBCXX_ALWAYS_INLINE value_type</span><br><span class="line"><span class="keyword">operator</span>++(<span class="keyword">int</span>) <span class="keyword">const</span> <span class="keyword">noexcept</span></span><br><span class="line">&#123; <span class="keyword">return</span> fetch_add(<span class="number">1</span>); &#125;</span><br><span class="line"></span><br><span class="line">value_type</span><br><span class="line"><span class="keyword">operator</span>++() <span class="keyword">const</span> <span class="keyword">noexcept</span></span><br><span class="line">&#123; <span class="keyword">return</span> __atomic_impl::__add_fetch(_M_ptr, value_type(<span class="number">1</span>)); &#125;</span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li><code>i++</code> 和 <code>++i</code> 不是原子操作，执行命令时包含内存读取，变量递增，回写内存三步，所以存在 <code>data race</code></li><li>即使被汇编成一句 <code>add DWORD PTR [rbp-4], 1</code>一句代码在多核CPU上也会导致结果的不确定性或错误</li><li>想要 <code>i++</code> 变成原子操作只需要定义成 <code>std::atomic</code> 模板类的对象即可，逻辑代码几乎无需修改</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/131354339" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>我们常常把求之不得的东西称之为理想~</p><p>2023-7-4 09:51:32</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;C++中的 &lt;code&gt;i++&lt;/code&gt; 和 &lt;code&gt;++i&lt;/code&gt; 这两种自增运算是不是原子操作，突然被这么一问竟有点不知所措，这么“简单”的操作应该是原子的吧，但是好像有读又有写应该不是原子操作，原子操作就是那种刷一下就能完成的操作，准确来描述就是一个操作不可再分，要完成都完成不能存在中间态，咦？怎么听起来和事务这么像？那么 &lt;code&gt;i++&lt;/code&gt; 和 &lt;code&gt;++i&lt;/code&gt; 是不是原子操作我们看它是否满足不可再分就行了。&lt;/p&gt;
    
    </summary>
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/categories/C-C/"/>
    
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/tags/C-C/"/>
    
      <category term="automic" scheme="http://AlbertGithubHome.github.io/blog/tags/automic/"/>
    
      <category term="竞态条件" scheme="http://AlbertGithubHome.github.io/blog/tags/%E7%AB%9E%E6%80%81%E6%9D%A1%E4%BB%B6/"/>
    
      <category term="自增操作" scheme="http://AlbertGithubHome.github.io/blog/tags/%E8%87%AA%E5%A2%9E%E6%93%8D%E4%BD%9C/"/>
    
  </entry>
  
  <entry>
    <title>C++访问std::variant类型数据的几种方式</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/06/23/C-%E8%AE%BF%E9%97%AEstd-variant%E7%B1%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/06/23/C-访问std-variant类型数据的几种方式/</id>
    <published>2023-06-23T03:59:10.000Z</published>
    <updated>2023-06-24T03:33:42.752Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p><code>std::variant</code>（可变体） 是 <code>C++17</code> 中引入的一个新的类模板，提供了一种存储不同类型的值的方式，类似于之前版本中的 <code>union</code>（联合体），但可以存储非 <code>POD</code> 类型和类对象，能够在运行时进行类型检查和转换，但具有更多的功能和更高的类型安全性，今天来看一下存储在<code>std::variant</code>中的数据要怎么读取。</p><a id="more"></a><h1 id="variant的简单使用"><a href="#variant的简单使用" class="headerlink" title="variant的简单使用"></a>variant的简单使用</h1><p>可以参考<a href="https://en.cppreference.com/w/cpp/utility/variant" target="_blank" rel="noopener">cppreference</a>网站的使用示例，也可以看看下面这个例子：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;variant&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::variant&lt;<span class="keyword">int</span>, <span class="keyword">double</span>, <span class="built_in">std</span>::<span class="built_in">string</span>&gt; value;</span><br><span class="line"></span><br><span class="line">    value = <span class="number">110</span>;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is an integer: "</span> &lt;&lt; <span class="built_in">std</span>::get&lt;<span class="keyword">int</span>&gt;(value) &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    value = <span class="number">0.618</span>;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is a double: "</span> &lt;&lt; <span class="built_in">std</span>::get&lt;<span class="keyword">double</span>&gt;(value) &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    value = <span class="string">"hello world"</span>;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is a string: "</span> &lt;&lt; <span class="built_in">std</span>::get&lt;<span class="built_in">std</span>::<span class="built_in">string</span>&gt;(value) &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// value = true;        // Compilation error: cannot convert type bool to any of the alternative types</span></span><br><span class="line">    <span class="comment">// std::get&lt;int&gt;(value) // std::bad_variant_access exception: value holds a different type</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在示例程序中，定义了一个 <code>std::variant</code> 对象 <code>value</code> 来存储整型、浮点型和字符串类型中的任意一种，然后分别将 <code>value</code> 赋值为整型、浮点型和字符串类型，并使用 <code>std::get</code> 来获取对应的值，此时可以正常打印 <code>value</code> 对象中存储的值</p><p>当我们试图将 value 赋值为其它未在定义变量时指定的类型时，编译器将会报编译错误，而当我我们试图获取 value 中不存在的类型的值时，程序将会抛出 std::bad_variant_access 异常，可以使用 try-catch 已经捕获。</p><p>通过这段代码我们可以得知，使用std::variant可以方便地存储多种类型的数据，并且能够在运行时进行类型检查和转换，这使得代码更加清晰易读，便于维护。</p><h1 id="variant相关函数和类"><a href="#variant相关函数和类" class="headerlink" title="variant相关函数和类"></a>variant相关函数和类</h1><ul><li>成员函数<ul><li><code>index</code>：返回 variant 中保存用类型的索引下标</li><li><code>valueless_by_exception</code>：返回 variant 是否处于因异常导致的无值状态</li><li><code>emplace</code>：原位构造 variant 中的值</li></ul></li><li>非成员函数<ul><li><code>visit</code>：通过调用variant保存类型值所提供的函数对象获取具体值</li><li><code>holds_alternative</code>：检查某个 variant 是否当前持有某个给定类型</li><li><code>std::get</code>：以给定索引或类型读取 variant 的值，错误时抛出异常</li><li><code>get_if</code>：以给定索引或类型，获得指向被指向的 variant 的值的指针，错误时返回空指针</li></ul></li><li>辅助类<ul><li><code>monostate</code>：用作非可默认构造类型的 variant 的首个可选项的占位符类型（预防一些类型不提供无参构造函数）</li><li><code>bad_variant_access</code>：非法地访问 variant 的值时抛出的异常</li><li><code>variant_npos</code>：非法状态的 variant 的下标</li></ul></li></ul><h1 id="访问std-variant数据"><a href="#访问std-variant数据" class="headerlink" title="访问std::variant数据"></a>访问std::variant数据</h1><p>从前面提到的例子和函数说明，我们可以看到有多种方式来访问std::variant数据，接一下来一起总结一下：</p><h2 id="std-get搭配index函数使用"><a href="#std-get搭配index函数使用" class="headerlink" title="std::get搭配index函数使用"></a>std::get搭配index函数使用</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;variant&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::variant&lt;<span class="keyword">double</span>, <span class="keyword">int</span>&gt; value = <span class="number">119</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="number">1</span> == value.index())</span><br><span class="line">        <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; <span class="built_in">std</span>::get&lt;<span class="number">1</span>&gt;(value) &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先用 <code>index()</code> 查询 variant保存的类型索引，然后在通过 <code>std::get&lt;NUMBER&gt;()</code> 获取其中的值</p><h2 id="std-get搭配std-holds-alternative函数使用"><a href="#std-get搭配std-holds-alternative函数使用" class="headerlink" title="std::get搭配std::holds_alternative函数使用"></a>std::get搭配std::holds_alternative函数使用</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;variant&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::variant&lt;<span class="keyword">double</span>, <span class="keyword">int</span>&gt; value = <span class="number">119</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">std</span>::holds_alternative&lt;<span class="keyword">int</span>&gt;(value))</span><br><span class="line">        <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; <span class="built_in">std</span>::get&lt;<span class="keyword">int</span>&gt;(value) &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>先通过 <code>std::holds_alternative()</code> 查询 variant保存的类型，然后在通过 <code>std::get&lt;TYPE&gt;()</code> 获取其中的值</p><h2 id="std-get-if函数"><a href="#std-get-if函数" class="headerlink" title="std::get_if函数"></a>std::get_if函数</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;variant&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::variant&lt;<span class="keyword">double</span>, <span class="keyword">int</span>&gt; value = <span class="number">119</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">auto</span> p = <span class="built_in">std</span>::get_if&lt;<span class="keyword">int</span>&gt;(&amp;value))</span><br><span class="line">        <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; *p &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>直接使用 <code>std::get_if</code> 函数获取对应值的指针，如果类型不匹配会返回空指针</p><h2 id="std-visit函数"><a href="#std-visit函数" class="headerlink" title="std::visit函数"></a>std::visit函数</h2><p>使用函数visit函数访问时，有点像使用<code>std::sort</code>这类函数，可以搭配自定义的结构（排序）重写<code>operator()</code>，让其变成可以被调用的函数对象，也可以定义lambda自带可执行特性。</p><h2 id="自定义访问结构的写法"><a href="#自定义访问结构的写法" class="headerlink" title="自定义访问结构的写法"></a>自定义访问结构的写法</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;variant&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::variant&lt;<span class="keyword">double</span>, <span class="keyword">int</span>&gt; value = <span class="number">119</span>;</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">VisitPackage</span></span></span><br><span class="line"><span class="class">    &#123;</span></span><br><span class="line">        <span class="function"><span class="keyword">auto</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span>&amp; v)</span> </span>&#123; <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; v &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>; &#125;</span><br><span class="line">        <span class="function"><span class="keyword">auto</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="keyword">const</span> <span class="keyword">int</span>&amp; v)</span> </span>&#123; <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; v &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>; &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">std</span>::visit(VisitPackage(), value);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="定义lambda函数组重载"><a href="#定义lambda函数组重载" class="headerlink" title="定义lambda函数组重载"></a>定义lambda函数组重载</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;variant&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span>... <span class="title">Ts</span>&gt; <span class="title">struct</span> <span class="title">overloaded</span> :</span> Ts... &#123; <span class="keyword">using</span> Ts::<span class="keyword">operator</span>()...; &#125;;</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span>... <span class="title">Ts</span>&gt; <span class="title">overloaded</span>(<span class="title">Ts</span>...) -&gt; <span class="title">overloaded</span>&lt;Ts...&gt;;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::variant&lt;<span class="keyword">double</span>, <span class="keyword">int</span>&gt; value = <span class="number">119</span>;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">std</span>::visit(overloaded &#123;</span><br><span class="line">      [] (<span class="keyword">const</span> <span class="keyword">double</span>&amp; v) &#123; <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; v &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>; &#125;,</span><br><span class="line">      [] (<span class="keyword">const</span> <span class="keyword">int</span>&amp; v) &#123; <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; v &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>; &#125;</span><br><span class="line">    &#125;, value);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这种方式将多个lambda放到一起形成重载，进而达到了访问variant数据的目的。</p><h1 id="overloaded是什么"><a href="#overloaded是什么" class="headerlink" title="overloaded是什么"></a>overloaded是什么</h1><p>上文例子中的最后一个中使用到了 <code>overloaded</code>，这令人眼花缭乱的写法着实很诡异，不过我们可以从头来分析一下，最后两个例子中等价的两部分是</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">VisitPackage</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line">    <span class="function"><span class="keyword">auto</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span>&amp; v)</span> </span>&#123; <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; v &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>; &#125;</span><br><span class="line">    <span class="function"><span class="keyword">auto</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="keyword">const</span> <span class="keyword">int</span>&amp; v)</span> </span>&#123; <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; v &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>; &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">VisitPackage()</span><br></pre></td></tr></table></figure><p>与</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span>... <span class="title">Ts</span>&gt; <span class="title">struct</span> <span class="title">overloaded</span> :</span> Ts... &#123; <span class="keyword">using</span> Ts::<span class="keyword">operator</span>()...; &#125;;</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span>... <span class="title">Ts</span>&gt; <span class="title">overloaded</span>(<span class="title">Ts</span>...) -&gt; <span class="title">overloaded</span>&lt;Ts...&gt;;</span></span><br><span class="line"></span><br><span class="line">overloaded &#123;</span><br><span class="line">    [] (<span class="keyword">const</span> <span class="keyword">double</span>&amp; v) &#123; <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; v &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>; &#125;,</span><br><span class="line">    [] (<span class="keyword">const</span> <span class="keyword">int</span>&amp; v) &#123; <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; <span class="string">"The value is: "</span> &lt;&lt; v &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>; &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>要想理解它们为什么等价，我们首先的得弄清楚lambda表达式是什么，在剖析lambda之前先来看看 <code>std::visit</code> 函数需要的参数是什么，分析<code>std::visit</code>的参数，先看 <code>struct VisitPackage</code> 结构更容易一些。</p><h2 id="std-visit的第一个参数"><a href="#std-visit的第一个参数" class="headerlink" title="std::visit的第一个参数"></a>std::visit的第一个参数</h2><p>通俗的来说<code>std::visit</code>的第一个参数需要的是一个可执行的对象，如果对象能被执行就需要实现 <code>operator()</code> 这个操作符，看起来像函数一样，这就是为什么在 <code>struct VisitPackage</code> 中定义了 <code>operator()</code>，并且定义了两个形成了参数不同的静态重载，作用就是为了在访问 <code>variant</code> 对象时适配不同的类型，在访问<code>variant</code> 对象时会选择最匹配的 <code>operator()</code> 函数，进而实现了访问variant中不同类型值行为不同的目的。</p><p>那lambda表达式能实现这个目的吗？我们接着往下看</p><h2 id="lambda-是什么"><a href="#lambda-是什么" class="headerlink" title="lambda 是什么"></a>lambda 是什么</h2><p>自从 C++11 引入lambda之后，对它赞美的声音不绝于耳，那lambda表达式究竟是怎样实现的呢？真的就是一个普通的函数吗？我们看一个小例子：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">int</span> x = <span class="number">5</span>, y = <span class="number">6</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">auto</span> func = [&amp;](<span class="keyword">int</span> n) &#123;</span><br><span class="line">    <span class="keyword">return</span> x + n;</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  func(<span class="number">7</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是一个使用lambda表达式简单的例子，代码中定义了一个int类型参数的返回值也是int的lambda函数，作用就是将外部变量x与函数参数的和返回，我们使用 <a href="https://cppinsights.io/s/b5fa7821" target="_blank" rel="noopener">cppinsights.io</a> 网站来将此段代码展开</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> x = <span class="number">5</span>;</span><br><span class="line">    <span class="keyword">int</span> y = <span class="number">6</span>;</span><br><span class="line"></span><br><span class="line">    <span class="class"><span class="keyword">class</span> __<span class="title">lambda_4_15</span></span></span><br><span class="line"><span class="class">    &#123;</span></span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        <span class="keyword">inline</span> <span class="comment">/*constexpr */</span> <span class="function"><span class="keyword">int</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="keyword">int</span> n)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function">        </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> x + n;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span>:</span><br><span class="line">        <span class="keyword">int</span> &amp; x;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span>:</span><br><span class="line">        __lambda_4_15(<span class="keyword">int</span> &amp; _x)</span><br><span class="line">        : x&#123;_x&#125;</span><br><span class="line">        &#123;&#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    __lambda_4_15 func = __lambda_4_15&#123;x&#125;;</span><br><span class="line">    func.<span class="keyword">operator</span>()(<span class="number">7</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以发现我们虽然定义了一个lambda函数，但是编译器为它生成了一个类 <code>__lambda_4_15</code>，生成了 <code>int&amp;</code> 类型的构造函数，并实现了 <code>operator</code>操作符，再调用lambda函数时先生成了 <code>__lambda_4_15</code>类的对象，再调用类的 <code>operator()</code>函数 <code>func.operator()(7);</code>，看到这里是不是似曾相识，虽然还不是很明白，但是和<code>struct VisitPackage</code>的定义总是有种说不清道不明的血缘关系。</p><p>弄清楚了lambda函数的本质，现在要实现的是怎么把多个lambda函数合成一个对象，并且让他们形成重载，因为lambda函数本质上存在一个类，只需要定义一个子类，继承多个lambda表达式就可以了，其实 <code>overloaded</code> 这个模板类就是为了实现这个目的。</p><h2 id="overloaded剖析"><a href="#overloaded剖析" class="headerlink" title="overloaded剖析"></a>overloaded剖析</h2><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span>... <span class="title">Ts</span>&gt; <span class="title">struct</span> <span class="title">overloaded</span> :</span> Ts... &#123; <span class="keyword">using</span> Ts::<span class="keyword">operator</span>()...; &#125;;</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span>... <span class="title">Ts</span>&gt; <span class="title">overloaded</span>(<span class="title">Ts</span>...) -&gt; <span class="title">overloaded</span>&lt;Ts...&gt;;</span></span><br></pre></td></tr></table></figure><p>一时间看起来很难理解，它来自 <a href="https://en.cppreference.com/w/cpp/utility/variant/visit" target="_blank" rel="noopener">en.cppreference.com</a> 中介绍 <code>std::visit</code> 访问 <code>std::variant</code> 的例子，可以换行看得更清楚一点：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// helper type for the visitor #4</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span>... <span class="title">Ts</span>&gt;</span></span><br><span class="line"><span class="class"><span class="title">struct</span> <span class="title">overloaded</span> :</span> Ts... &#123; <span class="keyword">using</span> Ts::<span class="keyword">operator</span>()...; &#125;;</span><br><span class="line"><span class="comment">// explicit deduction guide (not needed as of C++20)</span></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="class"><span class="keyword">class</span>... <span class="title">Ts</span>&gt;</span></span><br><span class="line"><span class="class"><span class="title">overloaded</span>(<span class="title">Ts</span>...) -&gt; <span class="title">overloaded</span>&lt;Ts...&gt;;</span></span><br></pre></td></tr></table></figure><blockquote><p>template<class... ts=""> struct overloaded : Ts… { using Ts::operator()…; };</class...></p><blockquote><p>这是一个类模板的声明，模板的名字是overloaded</p></blockquote></blockquote><p>分步拆解来看：</p><blockquote><p>template<class... ts=""> struct overloaded</class...></p><blockquote><p>表示类的模板参数为可变长的参数包 Ts<br>假设 Ts 包含 T1, T2, … , TN，那么这一句声明可以展开为：template<class t1,="" class="" t2,="" ...,="" tn=""> struct overloaded</class></p></blockquote><p>struct overloaded : Ts…</p><blockquote><p>表示类的基类为参数包 Ts 内所有的参数类型<br>假设 Ts 包含 T1, T2, … , TN，那么这一句声明可以展开为：struct overloaded : T1, T2, …, TN</p></blockquote><p>{ using Ts::operator()…; };</p><blockquote><p>这是一个函数体内的变长 using 声明<br>假设 Ts 包含 T1, T2, … , TN，那么这一句声明可以展开为：{ using T1::operator(), T1::operator(), …, TN::operator(); }<br>经过这步声明，overloaded 类的参数包 Ts 内所有的参数作为基类的成员函数operator()均被 overloaded 类引入了自己的作用域</p></blockquote><p>template<class... ts=""> overloaded(Ts…) -&gt; overloaded<ts...>;</ts...></class...></p><blockquote><p>这是一个自动推断向导说明，用于帮助编译器根据 overloaded 构造器参数的类型来推导 overloaded 的模板参数类型，C++17需要，C++20已不必写<br>它告诉编译器，如果 overloaded 构造器所有参数的类型的集合为Ts，那么 overloaded 的模板参数类型就是 Ts 所包含的所有类型<br>如果表达式a1, a2, …, an的类型分别为T1, T2, …, TN，那么构造器表达式overloaded x{a1, a2, …, an} 推导出，overloaded的类型就是 overloaded<t1, t2,="" ...,="" tn=""></t1,></p></blockquote></blockquote><p>经过这些解释，我们可以认为在最后一个例子中可能产生了类似这样的代码：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;variant&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> __<span class="title">lambda_12_7</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">inline</span> <span class="comment">/*constexpr */</span> <span class="function"><span class="keyword">void</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="keyword">const</span> <span class="keyword">double</span> &amp; v)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="built_in">std</span>::<span class="keyword">operator</span>&lt;&lt;(<span class="built_in">std</span>::<span class="built_in">cout</span>, <span class="string">"The value is: "</span>).<span class="keyword">operator</span>&lt;&lt;(v).<span class="keyword">operator</span>&lt;&lt;(<span class="built_in">std</span>::<span class="built_in">endl</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> __<span class="title">lambda_13_7</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="keyword">inline</span> <span class="comment">/*constexpr */</span> <span class="function"><span class="keyword">void</span> <span class="title">operator</span><span class="params">()</span><span class="params">(<span class="keyword">const</span> <span class="keyword">int</span> &amp; v)</span> <span class="keyword">const</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="built_in">std</span>::<span class="keyword">operator</span>&lt;&lt;(<span class="built_in">std</span>::<span class="built_in">cout</span>, <span class="string">"The value is: "</span>).<span class="keyword">operator</span>&lt;&lt;(v).<span class="keyword">operator</span>&lt;&lt;(<span class="built_in">std</span>::<span class="built_in">endl</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;&gt;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">overloaded</span>&lt;__lambda_12_7, __lambda_13_7&gt; :</span> <span class="keyword">public</span> __lambda_12_7, <span class="keyword">public</span> __lambda_13_7</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">using</span> __lambda_12_7::<span class="keyword">operator</span>();</span><br><span class="line">    <span class="comment">// inline /*constexpr */ void ::operator()(const double &amp; v) const;</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">using</span> __lambda_13_7::<span class="keyword">operator</span>();</span><br><span class="line">    <span class="comment">// inline /*constexpr */ void ::operator()(const int &amp; v) const;</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::variant&lt;<span class="keyword">double</span>, <span class="keyword">int</span>&gt; value = <span class="built_in">std</span>::variant&lt;<span class="keyword">double</span>, <span class="keyword">int</span>&gt;(<span class="number">119</span>);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">std</span>::visit(overloaded&#123;__lambda_12_7(__lambda_12_7&#123;&#125;), __lambda_13_7(__lambda_13_7&#123;&#125;)&#125;, value);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li><code>std::variant</code> 可以存储多个类型的值，并且它会自动处理类型转换和内存分配</li><li><code>std::variant</code> 可以存储非 <code>POD</code> 类型和类对象，能够在运行时进行类型检查和转换，具有更高的类型安全性</li><li>可以使用 <code>std::visit</code> 全局函数来访问 <code>std::variant</code> 中存储的值，该函数根据存储的值的类型自动选择调用哪个函数对象</li><li>可以使用 <code>std::holds_alternative</code> 函数来检查变量中是否存储了特定的类型</li><li>定义lambda函数时，编译器会为其生成一个类</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/131277428" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>代码是程序世界的运行规则，一个个字母的敲击正在规定着程序世界的运行秩序，无论代码写的天花乱坠，究其本质还是规定这一步要做什么，下一步继续做什么而已~</p><p>2023-6-23 21:13:04</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;&lt;code&gt;std::variant&lt;/code&gt;（可变体） 是 &lt;code&gt;C++17&lt;/code&gt; 中引入的一个新的类模板，提供了一种存储不同类型的值的方式，类似于之前版本中的 &lt;code&gt;union&lt;/code&gt;（联合体），但可以存储非 &lt;code&gt;POD&lt;/code&gt; 类型和类对象，能够在运行时进行类型检查和转换，但具有更多的功能和更高的类型安全性，今天来看一下存储在&lt;code&gt;std::variant&lt;/code&gt;中的数据要怎么读取。&lt;/p&gt;
    
    </summary>
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/categories/C-C/"/>
    
    
      <category term="C/C++" scheme="http://AlbertGithubHome.github.io/blog/tags/C-C/"/>
    
      <category term="variant" scheme="http://AlbertGithubHome.github.io/blog/tags/variant/"/>
    
      <category term="Union" scheme="http://AlbertGithubHome.github.io/blog/tags/Union/"/>
    
      <category term="visit" scheme="http://AlbertGithubHome.github.io/blog/tags/visit/"/>
    
  </entry>
  
  <entry>
    <title>试试InsCode这个在线跑项目代码的平台</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/06/18/%E8%AF%95%E8%AF%95InsCode%E8%BF%99%E4%B8%AA%E5%9C%A8%E7%BA%BF%E8%B7%91%E9%A1%B9%E7%9B%AE%E4%BB%A3%E7%A0%81%E7%9A%84%E5%B9%B3%E5%8F%B0/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/06/18/试试InsCode这个在线跑项目代码的平台/</id>
    <published>2023-06-18T13:47:24.000Z</published>
    <updated>2023-06-18T15:43:18.269Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近看CSDN上线了InsCode这个在线跑项目代码的平台，放在了主菜单主推位置，上次这个位置好像是Gitee，在上次应该是代码片，CSDN 作为我一直使用的产品，最近十年我经历了他所有的变革，就像我刚刚提到的这些主菜单的变化，很多的东西已经被抛弃了，希望这次的InsCode能长久的支持下去。</p><a id="more"></a><p>代码开源平台并不好做，Github最大最活跃但访问并不顺畅，国内最早用的TaoCode在2018年关闭，后来Gitee用的比较多，但审查严格时候有多个开源项目被隐藏，CSDN现在主推的是GitCode，希望也能长久的支持下去，我并不太可能做出轰轰烈烈的开源项目分享，但分享一些代码世界的只言片语也是我的愿望，但愿能帮助一些恰好需要的人。</p><h1 id="先了解一下InsCode"><a href="#先了解一下InsCode" class="headerlink" title="先了解一下InsCode"></a>先了解一下InsCode</h1><p>InsCode服务是一个在线编程工具，旨在为开发者提供一个便捷的编写、运行和分享代码的环境，让开发者无需在本地搭建编程环境，即可快速编写和运行代码。</p><p>InsCode支持多种编程语言，包括Java、Python、C++、JavaScript等代码，支持一些方便的项目模板，但目前还不支持C++模板希望后面能补充上。它提供了完整的运行环境，让代码在网页上直接运行并输出结果，即时调试，方便快捷，并且还提供了现实免费的InsCode AI Chat，帮助我们快速实现功能。同时，Inscode还提供了分享功能，可以轻松地将代码分享给其他人。</p><h1 id="写一个Python示例"><a href="#写一个Python示例" class="headerlink" title="写一个Python示例"></a>写一个Python示例</h1><p>本来想写一段C++示例的，但是因为目前还不支持C++模板，所以先写个Python的例子展示一下使用方式吧，比如想打印本机IP地址，可以借助 <code>InsCode AI Chat</code> 生成，直接点击插入即可。</p><p><img src="https://img-blog.csdnimg.cn/bc98c66b190c483598a78c4f465128ba.png" alt="查询外网IP"><br>直接点击运行按钮查看效果，打印出外网IP是<code>116.198.41.215</code></p><p><img src="https://img-blog.csdnimg.cn/0bbeb2b0157f4777b20629e9c209c6b7.png" alt="运行结果">整个过程我真的是一行代码都不用写了，最终还可以将作品打包放到博客中展示，因为这个示例项目还在审核中，等审核通过后我会把效果贴上来。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>InsCode服务是一个在线编程工具，旨在为开发者提供一个便捷的编写、运行和分享代码的环境</li><li>InsCode支持多种编程语言，包括Java、Python、C++、JavaScript等代码，并支持一些方便的项目模板</li><li>使用InsCode真的可以做到不写一行代码来发布自己的产品，虽然不是很完善，但应该会越来越好</li><li>各大平台都在AI赛道里竞争起来，真正做出相关产品的并不多，我们一起期待吧</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/131262907" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>三个人确实就比两个人好看，这其中隐含的就是团圆的情感~</p><p>2023-06-18 23:26:00</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;最近看CSDN上线了InsCode这个在线跑项目代码的平台，放在了主菜单主推位置，上次这个位置好像是Gitee，在上次应该是代码片，CSDN 作为我一直使用的产品，最近十年我经历了他所有的变革，就像我刚刚提到的这些主菜单的变化，很多的东西已经被抛弃了，希望这次的InsCode能长久的支持下去。&lt;/p&gt;
    
    </summary>
    
      <category term="Platform" scheme="http://AlbertGithubHome.github.io/blog/categories/Platform/"/>
    
    
      <category term="C++" scheme="http://AlbertGithubHome.github.io/blog/tags/C/"/>
    
      <category term="InsCode" scheme="http://AlbertGithubHome.github.io/blog/tags/InsCode/"/>
    
      <category term="在线运行" scheme="http://AlbertGithubHome.github.io/blog/tags/%E5%9C%A8%E7%BA%BF%E8%BF%90%E8%A1%8C/"/>
    
      <category term="CSDN" scheme="http://AlbertGithubHome.github.io/blog/tags/CSDN/"/>
    
  </entry>
  
  <entry>
    <title>初识Telegraf、InfluxDB和Grafana铁三角形成的监控可视化解决方案</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/06/17/%E5%88%9D%E8%AF%86Telegraf%E3%80%81InfluxDB%E5%92%8CGrafana%E9%93%81%E4%B8%89%E8%A7%92%E5%BD%A2%E6%88%90%E7%9A%84%E7%9B%91%E6%8E%A7%E5%8F%AF%E8%A7%86%E5%8C%96%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/06/17/初识Telegraf、InfluxDB和Grafana铁三角形成的监控可视化解决方案/</id>
    <published>2023-06-17T11:39:56.000Z</published>
    <updated>2023-06-18T12:45:34.539Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>数据监控目前用于各行各业，服务器开发也不例外，进程什么时候被杀死了？内存占用已经达到98%了怎么早点没发现，网络带宽已经占满了怪不得玩家反馈说登录不上去了，这些都是服务器开发人员常常听到的“抱怨”，他们都涉及到服务器资源的使用情况，如果有一种方便的途径可以高效的监控各个服务器的情况就好了，单靠人去各个服务器查看是不现实的，今天就来简单介绍一种我刚刚学到的方案。</p><a id="more"></a><h1 id="原始的监控靠人盯"><a href="#原始的监控靠人盯" class="headerlink" title="原始的监控靠人盯"></a>原始的监控靠人盯</h1><p>如果服务器只有一台或者很少，可以每隔一段时间去服务器上看看，<code>top</code> 看一下整体运行情况，<code>free</code> 查一下内存详情，<code>df</code> 看一下硬盘还够不够，<code>iftop</code> 看一下网络带宽情况，不要觉得不可能，有次测试我真的就是开了好几个远程窗口来盯着各个指标，不过人的精力有限，这样做毕竟不是长久之计。</p><h1 id="进化的监控靠批处理脚本"><a href="#进化的监控靠批处理脚本" class="headerlink" title="进化的监控靠批处理脚本"></a>进化的监控靠批处理脚本</h1><p>随着服务器的增多，靠人盯的方式的已经达不到要求了，看看上面的这些命令都是已知的，并且只需要定期得到这些值就可以了，完全可以使用脚本定期执行来做呀，把上面提到的命令封装成脚本然后定期执行得到结果列表，然后发送到指定的地址，我们就可以只分析这些结果，没必要登录到各个服务器上去查询了。</p><h1 id="高端的监控靠完整的可视化解决方案"><a href="#高端的监控靠完整的可视化解决方案" class="headerlink" title="高端的监控靠完整的可视化解决方案"></a>高端的监控靠完整的可视化解决方案</h1><p>懒惰是科学发展的推动力，使用脚本收集数据就不用登录服务器去实时查询了，那能不能帮我自动分析结果，或者在异常时直接给出提醒，因为我连分析的操作都不想做，肯定有人这样想当然也有人这样做了，并且形成了成熟的解决方案，我们只需要简单搭建和配置就可以早点下班了，半夜收到服务器报警电话时起来解决就可以了。</p><h1 id="Telegraf、InfluxDB和Grafana铁三角"><a href="#Telegraf、InfluxDB和Grafana铁三角" class="headerlink" title="Telegraf、InfluxDB和Grafana铁三角"></a>Telegraf、InfluxDB和Grafana铁三角</h1><p>Telegraf是一个用于收集、处理和发送指标数据的代理，它可以与各种数据源和监控系统集成，定期收集数据并将其发送到目标位置。</p><p>InfluxDB是一个开源的时间序列数据库，用于存储和查询大量时间序列数据。它被广泛用于存储来自Telegraf等数据收集器的指标数据。</p><p>Grafana用于创建交互式仪表板，从各种数据源中提取数据，并将数据以图表、图形和表格等形式展示出来。</p><p>通过将Telegraf用作数据收集代理，将数据发送到InfluxDB中存储，然后使用Grafana从InfluxDB中提取数据进行可视化，这个铁三角组合提供了一个强大的监控和数据可视化解决方案。</p><h2 id="Telegraf"><a href="#Telegraf" class="headerlink" title="Telegraf"></a>Telegraf</h2><p>Telegraf是一个开源的代理程序，用于收集、处理和发送指标数据。它设计用于高效地收集各种类型的数据，并将其发送到不同的目标位置，如时序数据库、消息队列等，具有以下是关键特点和功能：</p><ul><li><p>插件架构：Telegraf采用模块化的插件架构，具有丰富的输入和输出插件。它支持多种输入插件，如系统性能指标、日志文件、数据库查询、API调用等，以便从不同的数据源中收集数据。同样，Telegraf还支持多种输出插件，如InfluxDB、Elasticsearch、Kafka等，用于将收集到的数据发送到不同的目标位置。</p></li><li><p>多平台支持：Telegraf可在多个操作系统上运行，包括Linux、Windows和macOS，以适应不同的部署环境和需求。</p></li><li><p>轻量级和高性能：Telegraf被设计为轻量级和高性能的代理程序，可以在资源受限的环境中高效运行。它使用Golang编写，具有低的内存和CPU占用，并能够处理高并发的数据收集和处理任务。</p></li><li><p>数据转换和处理：Telegraf提供了灵活的数据转换和处理功能，可以对收集到的数据进行过滤、转换、聚合等操作。它支持各种数据处理任务，如标签添加、数据格式转换、聚合计算等，以便对数据进行预处理和清洗，使其适应目标存储或分析平台的要求。</p></li><li><p>插件生态系统：Telegraf拥有一个活跃的插件生态系统，用户可以根据自己的需求选择和配置适当的插件。社区提供了大量的插件，覆盖了各种数据源和目标位置，使得Telegraf具备广泛的适用性和灵活性。</p></li></ul><h2 id="InfluxDB"><a href="#InfluxDB" class="headerlink" title="InfluxDB"></a>InfluxDB</h2><p>InfluxDB是一个开源的高性能时序数据库，专门用于存储和处理时间序列数据。它被设计为能够高效地处理大规模的时间相关数据，并提供快速的数据插入、查询和聚合能力，具有关键特点和功能：</p><ul><li><p>时序数据存储：InfluxDB专注于存储和管理时间序列数据，如传感器数据、监控指标、日志事件等。它使用特定的数据结构和索引机制，以优化对时间序列数据的写入和查询操作。</p></li><li><p>高性能和可扩展性：InfluxDB被设计为具有高吞吐量和低延迟的数据库系统。它采用了一系列的优化策略，包括压缩算法、分片存储、数据分区等，以实现高效的数据处理和查询性能。此外，InfluxDB还支持水平扩展，可以通过添加更多节点来提高性能和容量。</p></li><li><p>查询语言：InfluxDB提供了InfluxQL和Flux两种查询语言。InfluxQL是类似于SQL的查询语言，专门用于时间序列数据的查询和聚合操作。Flux是一种功能更强大和灵活的查询语言，它支持更复杂的数据处理和转换操作，并具有扩展性更好的能力。</p></li><li><p>数据保留策略：InfluxDB允许定义数据保留策略，以指定数据在数据库中的保存期限和存储策略。这样可以根据数据的时效性需求和存储资源限制来管理数据的保留和清理。</p></li><li><p>数据一致性和持久性：InfluxDB采用了写时复制（Write Ahead Log）的机制，确保数据的一致性和持久性。它使用持久化引擎来保证数据在故障情况下的可靠性，同时提供数据备份和恢复的功能。</p></li><li><p>整合生态系统：InfluxDB具有丰富的整合生态系统，可以与各种数据源和工具集成。它支持与Telegraf等数据收集代理的无缝集成，以及与Grafana等数据可视化工具的联合使用，提供完整的监控和数据处理解决方案。</p></li></ul><h2 id="Grafana"><a href="#Grafana" class="headerlink" title="Grafana"></a>Grafana</h2><p>Grafana是一个开源的数据可视化和监控平台，用于创建、展示和分析实时指标和数据的交互式仪表板。它提供了丰富的可视化选项和灵活的配置，使用户能够轻松地创建各种类型的仪表板，并实时监控和分析数据，具有以下关键特点和功能：</p><ul><li><p>数据源支持：Grafana支持广泛的数据源，包括各种数据库（如MySQL、PostgreSQL、InfluxDB）、时序数据库（如Prometheus、Graphite）、日志文件、API等。它提供了内置的数据源插件和API接口，以便与不同的数据源进行集成和数据获取。</p></li><li><p>丰富的可视化选项：Grafana提供多种可视化选项，如折线图、柱状图、仪表盘、热力图等，可以根据数据的特点选择适合的图表类型。它还支持动态图表、图表注释、数据标记等功能，以增强数据的可视化效果和交互性。</p></li><li><p>交互式仪表板：Grafana的仪表板具有交互式的功能，用户可以自由地缩放、选择时间范围、过滤数据等。它还支持实时数据更新和自动刷新，以确保仪表板上的数据保持最新。</p></li><li><p>告警和通知：Grafana提供了强大的告警功能，用户可以根据自定义的条件设置告警规则，并通过电子邮件、Slack等渠道接收告警通知。这样可以及时发现数据异常或重要指标的变化。</p></li><li><p>用户管理和权限控制：Grafana支持多用户和团队管理，可以设置不同的用户角色和权限，以控制对仪表板和数据的访问和操作。它还提供了LDAP、OAuth等身份验证和单点登录功能，与企业的用户管理系统集成更加便捷。</p></li><li><p>社区支持和插件生态系统：Grafana拥有庞大的开源社区支持和活跃的插件生态系统。用户可以从社区中获取各种主题、模板和插件，以扩展和定制Grafana的功能和外观。</p></li></ul><h1 id="Grafana仪表板展示"><a href="#Grafana仪表板展示" class="headerlink" title="Grafana仪表板展示"></a>Grafana仪表板展示</h1><p>说实话第一次看到Grafana仪表板还是挺震撼的，展示的信息很多，可以帮助服务器维护人员分析很多问题，下面来展示一些主要的面板。</p><h2 id="服务器资源总览"><a href="#服务器资源总览" class="headerlink" title="服务器资源总览"></a>服务器资源总览</h2><p><img src="https://img-blog.csdnimg.cn/2baa1c032a6c48d6b6ff0ee634c4268f.png" alt="服务器总览"></p><h2 id="负载和内存使用"><a href="#负载和内存使用" class="headerlink" title="负载和内存使用"></a>负载和内存使用</h2><p><img src="https://img-blog.csdnimg.cn/f622fa54e73a400c857d697d047dd031.png" alt="负载和内存"></p><h2 id="网络带宽"><a href="#网络带宽" class="headerlink" title="网络带宽"></a>网络带宽</h2><p><img src="https://img-blog.csdnimg.cn/bf5f90b7524b43449ff26d01057ce636.png" alt="网络带宽"></p><h2 id="磁盘IO"><a href="#磁盘IO" class="headerlink" title="磁盘IO"></a>磁盘IO</h2><p><img src="https://img-blog.csdnimg.cn/fc2d904cb3684df380f84ec5d19e7d48.png" alt="磁盘IO"></p><h2 id="IO延迟"><a href="#IO延迟" class="headerlink" title="IO延迟"></a>IO延迟</h2><p><img src="https://img-blog.csdnimg.cn/3a4a5c9b3860410a8776a89a2326c86f.png" alt="IO延迟"></p><h2 id="其他指标"><a href="#其他指标" class="headerlink" title="其他指标"></a>其他指标</h2><p><img src="https://img-blog.csdnimg.cn/9d22f9db6a76477e981a81d8ac90135d.png" alt="其他指标"></p><h2 id="进程信息"><a href="#进程信息" class="headerlink" title="进程信息"></a>进程信息</h2><p><img src="https://img-blog.csdnimg.cn/986fefd7d3464d6eb3919b7e67ecdf9b.png" alt="进程信息"></p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>Telegraf、Grafana和InfluxDB通常被称为”铁三角”，因为它们经常一起使用，形成一个完整的监控和数据可视化解决方案</li><li>Telegraf是一个用于收集、处理和发送指标数据的代理，它可以与各种数据源和监控系统集成，定期收集数据并将其发送到目标位置</li><li>InfluxDB是一个开源的时间序列数据库，用于存储和查询大量时间序列数据。它被广泛用于存储来自Telegraf等数据收集器的指标数据</li><li>Grafana用于创建交互式仪表板，从各种数据源中提取数据，并将数据以图表、图形和表格等形式展示出来</li><li>Grafana的仪表板真的挺漂亮的</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/130569748" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>把烦恼痛了吞了认了算了<br>不对别人讲<br>谁还没有辜负几段 昂贵的时光</p><p>2023-6-17 19:39:56</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;数据监控目前用于各行各业，服务器开发也不例外，进程什么时候被杀死了？内存占用已经达到98%了怎么早点没发现，网络带宽已经占满了怪不得玩家反馈说登录不上去了，这些都是服务器开发人员常常听到的“抱怨”，他们都涉及到服务器资源的使用情况，如果有一种方便的途径可以高效的监控各个服务器的情况就好了，单靠人去各个服务器查看是不现实的，今天就来简单介绍一种我刚刚学到的方案。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/tags/linux/"/>
    
      <category term="服务器" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
      <category term="Telegraf" scheme="http://AlbertGithubHome.github.io/blog/tags/Telegraf/"/>
    
      <category term="InfluxDB" scheme="http://AlbertGithubHome.github.io/blog/tags/InfluxDB/"/>
    
      <category term="Grafana" scheme="http://AlbertGithubHome.github.io/blog/tags/Grafana/"/>
    
  </entry>
  
  <entry>
    <title>linux环境下熟悉又陌生的sudo命令</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/05/26/linux%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%86%9F%E6%82%89%E5%8F%88%E9%99%8C%E7%94%9F%E7%9A%84sudo%E5%91%BD%E4%BB%A4/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/05/26/linux环境下熟悉又陌生的sudo命令/</id>
    <published>2023-05-26T14:08:38.000Z</published>
    <updated>2023-06-18T12:47:10.813Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p><code>sudo</code>命令应该是任何一个服务器使用人员都绕不开的命令，比如那个大名鼎鼎的删库跑路命令 <code>sudo rm -rf /*</code>，虽然经常使用，也知道用它能做一些普通用户无法完成的事情，那么它究竟是什么意思，有什么作用和好处，和超级无敌的 <code>root</code> 用户又有什么关系呢？接下来我们来简单梳理一下。</p><a id="more"></a><h1 id="关于sudo的一些疑问"><a href="#关于sudo的一些疑问" class="headerlink" title="关于sudo的一些疑问"></a>关于sudo的一些疑问</h1><h2 id="sudo是什么"><a href="#sudo是什么" class="headerlink" title="sudo是什么"></a>sudo是什么</h2><p><code>sudo</code> 是一种在类 Unix 系统中使用的命令，用于以超级用户（superuser）的身份执行其他命令。它是 “Super User Do” 的缩写。超级用户通常是系统管理员，具有对操作系统的完全控制权。</p><p>当你以普通用户的身份登录到系统时，可能没有足够的权限来执行某些敏感的系统操作或访问某些文件。在这种情况下，可以使用 <code>sudo</code> 命令在临时提升权限的情况下执行特定命令。</p><p>使用 <code>sudo</code> 命令时，需要输入管理员密码以验证你的身份。一旦验证成功，就可以以超级用户的权限执行命令。这样可以确保系统安全，因为只有经过授权的用户才能执行特权操作。</p><p>例如，如果想要安装软件包或修改系统设置，但这些操作需要超级用户权限，你可以在命令前加上 <code>sudo</code>，然后输入密码以执行该命令。</p><p>以下是一个sudo命令使用的示例：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install git</span><br></pre></td></tr></table></figure></p><p>这个命令将使用 <code>apt-get</code> 工具安装一个软件包，由于它需要对系统进行修改，所以需要超级用户权限。通过使用 <code>sudo</code>，可以在普通用户的身份下临时获取这些权限来执行该操作。</p><h2 id="sudo-和-root-的区别和联系"><a href="#sudo-和-root-的区别和联系" class="headerlink" title="sudo 和 root 的区别和联系"></a>sudo 和 root 的区别和联系</h2><p><code>sudo</code> 和 <code>root</code> 在类 Unix 系统中具有不同的角色和功能。以下是它们之间的区别和联系：</p><ol><li><p><strong>权限级别：</strong> <code>root</code> 是超级用户，也称为系统管理员。拥有 <code>root</code> 权限意味着拥有对整个系统的完全控制权，可以执行所有操作并访问所有文件。相比之下，<code>sudo</code> 是一个命令，允许普通用户在需要时以超级用户的身份执行特定的命令。</p></li><li><p><strong>使用方式：</strong> <code>root</code> 用户可以直接登录到系统并拥有持久的超级用户权限。而 <code>sudo</code> 是一种临时提升权限的机制，允许普通用户在需要时以超级用户的身份执行特定命令。普通用户可以使用 <code>sudo</code> 命令来运行特权命令，但在执行时需要输入管理员密码进行身份验证。</p></li><li><p><strong>安全性：</strong> <code>sudo</code> 的使用可以提高系统的安全性。通过限制普通用户的特权，只有在需要执行特定任务时才临时提升权限，可以减少意外或恶意操作造成的潜在风险。相比之下，<code>root</code> 用户拥有系统的完全权限，因此使用 <code>root</code> 用户执行命令时需要格外小心，以免对系统造成损坏。</p></li><li><p><strong>日常操作：</strong> 为了提高系统安全性，通常建议使用 <code>sudo</code> 来执行需要特权的命令，而不是直接使用 <code>root</code> 用户。使用 <code>sudo</code> 可以在普通用户的身份下执行特权命令，并避免了在常规操作中长时间保持 <code>root</code> 权限，从而减少了潜在的错误或安全风险。</p></li></ol><p>总结来说，<code>root</code> 是系统的超级用户，拥有完全的权限，而 <code>sudo</code> 是一种临时提升权限的机制，允许普通用户以超级用户的身份执行特定命令。使用 <code>sudo</code> 可以提高系统的安全性，同时避免在日常操作中过多使用 <code>root</code> 用户带来的潜在风险。</p><h2 id="使用-sudo-的好处"><a href="#使用-sudo-的好处" class="headerlink" title="使用 sudo 的好处"></a>使用 sudo 的好处</h2><p>使用 <code>sudo</code> 有以下几个好处：</p><ol><li><p><strong>安全性：</strong> <code>sudo</code> 增加了系统的安全性。通过限制普通用户的特权，只有在需要执行特定任务时才临时提升权限，可以减少潜在的错误或恶意操作对系统造成的风险。普通用户在执行需要特权的命令时需要输入管理员密码，这种身份验证确保只有经过授权的用户才能执行特权操作。</p></li><li><p><strong>权限控制：</strong> <code>sudo</code> 允许系统管理员细粒度地控制用户对系统资源和命令的访问权限。管理员可以通过配置 <code>sudo</code> 的策略文件，精确地指定哪些用户可以执行哪些命令以及以何种方式执行。这种权限控制有助于实施最小权限原则，即用户只能以最低权限执行任务，从而降低了潜在的安全风险。</p></li><li><p><strong>审计和日志记录：</strong> <code>sudo</code> 提供了审计和日志记录功能，可以记录执行特权操作的用户、时间和执行的命令。这对于追踪系统中的活动、故障排查和安全审计非常有价值。通过审计和日志记录，管理员可以了解特权操作的细节，以及哪些用户在何时执行了哪些命令。</p></li><li><p><strong>限制特权操作：</strong> 使用 <code>sudo</code> 可以限制特权操作的范围。普通用户只有在需要执行特定命令时才能使用 <code>sudo</code> 提升权限。这种限制可以避免用户误操作或滥用超级用户权限，从而减少了对系统的潜在风险。</p></li></ol><p>总而言之，使用 <code>sudo</code> 可以提高系统的安全性、实施最小权限原则、提供审计和日志记录功能，并限制特权操作的范围。这使得系统管理员能够更好地管理用户权限并降低潜在的风险。</p><h2 id="怎样查看sudo命令日志"><a href="#怎样查看sudo命令日志" class="headerlink" title="怎样查看sudo命令日志"></a>怎样查看sudo命令日志</h2><p>可以使用适当的文本编辑器打开 sudo 日志文件：选择适用于你的系统的文本编辑器（如 nano、vim、或者其他）以超级用户权限打开 sudo 日志文件。日志文件的位置和名称可能会因操作系统的不同而异。下面是一些常见的 sudo 日志文件路径：</p><ul><li><p>Ubuntu、Debian 等基于 Debian 的系统：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nano /var/log/auth.log</span><br></pre></td></tr></table></figure></li><li><p>CentOS、Fedora、Red Hat 等基于 Red Hat 的系统：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nano /var/log/secure</span><br></pre></td></tr></table></figure></li><li><p>macOS：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo nano /var/log/system.log</span><br></pre></td></tr></table></figure></li></ul><p>查看 sudo 日志文件通常需要管理员或超级用户权限。确保你具有足够的权限才能访问和查看日志文件。此外，一些操作系统可能还提供了特定的命令或工具来查看系统日志，例如 <code>journalctl</code> 命令在某些 Linux 发行版中可用于查看系统日志。你可以根据你的操作系统和日志管理方式来选择适合的方法来查看 sudo 日志。</p><h1 id="修改sudo配置"><a href="#修改sudo配置" class="headerlink" title="修改sudo配置"></a>修改sudo配置</h1><p>sudo的配置文件是 <code>/etc/sudoers</code> 但是不建议直接用文本工具打开它，一旦修改出错可能导致很多严重问题，可以使用 <code>visudo</code> 命令进行打开编辑，使用 <code>visudo</code> 命令编辑 sudoers 文件时，系统会执行语法检查以确保配置的正确性，以避免可能的权限问题，下面展示一个简单配置样例：</p><h2 id="简单样例"><a href="#简单样例" class="headerlink" title="简单样例"></a>简单样例</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># User privilege specification</span><br><span class="line">root    ALL=(ALL:ALL) ALL</span><br><span class="line"></span><br><span class="line"># Allow members of group sudo to execute any command</span><br><span class="line">%sudo   ALL=(ALL:ALL) ALL</span><br><span class="line"></span><br><span class="line"># Allow members of group admin to execute any command</span><br><span class="line">%admin  ALL=(ALL:ALL) ALL</span><br><span class="line"></span><br><span class="line"># Allow specific user to run specific command as another user</span><br><span class="line">johndoe ALL=(appuser) /usr/local/bin/git</span><br></pre></td></tr></table></figure><p><code>visudo</code> 是一个用于编辑 sudoers 文件的命令，该文件包含了与 sudo（以管理员权限执行命令）相关的配置信息。在 sudoers 文件中，每一行都由多个列组成，其中包含不同的字段和含义。以下是 sudoers 文件中各列的一般含义：</p><ol><li><p>列1：用户列表</p><ul><li>该列指定了可以使用 sudo 命令的用户或用户组。</li><li>可以是单个用户名，如 “bob”。</li><li>可以是用户组前缀 “%” 后跟用户组名，如 “%admin”。</li></ul></li><li><p>列2：主机列表</p><ul><li>该列指定了可以在哪些主机上使用 sudo 命令。</li><li>可以是单个主机名，如 “localhost”。</li><li>可以是主机名前缀 “ALL”，表示适用于所有主机。</li></ul></li><li><p>列3：=(运行 as) 列表</p><ul><li>该列指定了可以以哪个用户身份来运行命令。</li><li>可以是单个用户名，如 “root”。</li><li>可以是用户组前缀 “ALL”，表示可以以任何用户身份运行命令。</li><li>=(ALL:ALL)：表示可以以任何用户身份 (ALL) 在任何终端 (ALL) 上运行命令。</li></ul></li><li><p>列4：NOPASSWD: 标志</p><ul><li>该列指定了是否需要输入密码才能使用 sudo 命令。</li><li>如果列中包含 “NOPASSWD:”，则表示不需要密码。</li><li>如果列为空或未包含 “NOPASSWD:”，则需要输入密码。</li></ul></li><li><p>列5及以后：命令列表</p><ul><li>该列指定了可以使用 sudo 命令执行的具体命令。</li><li>可以是完整的命令路径，如 “/usr/bin/apt-get”。</li><li>可以是通配符，如 “/usr/bin/*” 表示该目录下的所有命令。</li></ul></li></ol><h2 id="复杂样例及解释"><a href="#复杂样例及解释" class="headerlink" title="复杂样例及解释"></a>复杂样例及解释</h2><p>下面是一个复杂一些的 visudo 配置示例，其中包含了多个用户、用户组和命令的授权规则：</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># User privilege specification</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## Allow root to run any commands on all hosts</span></span><br><span class="line">root    ALL=(ALL:ALL) ALL</span><br><span class="line"></span><br><span class="line"><span class="comment">## Allow members of the admin group to run any commands on all hosts</span></span><br><span class="line">%admin  ALL=(ALL:ALL) ALL</span><br><span class="line"></span><br><span class="line"><span class="comment">## Allow specific user to run specific commands as root without password</span></span><br><span class="line">johndoe ALL=(root) NOPASSWD: /bin/ls, /usr/bin/apt-get</span><br><span class="line"></span><br><span class="line"><span class="comment">## Allow specific user to run commands in specific directories as another user</span></span><br><span class="line">johndoe ALL=(appuser) /opt/app/bin/*</span><br><span class="line">johndoe ALL=(backupuser) /var/backup/scripts/*</span><br><span class="line"></span><br><span class="line"><span class="comment">## Allow specific user to run specific command with arguments as root</span></span><br><span class="line">johndoe ALL=(root) /usr/sbin/usermod [a-z]*</span><br><span class="line"></span><br><span class="line"><span class="comment">## Deny specific user to run any command</span></span><br><span class="line">banneduser ALL=(ALL:ALL) ALL, !/usr/bin/*</span><br><span class="line"></span><br><span class="line"><span class="comment">## Allow members of the wheel group to run any commands on localhost without password</span></span><br><span class="line">%wheel  localhost=(ALL:ALL) NOPASSWD: ALL</span><br><span class="line"></span><br><span class="line"><span class="comment">## Allow users in the developer group to run specific commands with arguments</span></span><br><span class="line">%developer ALL=(ALL:ALL) /usr/bin/git *, /usr/bin/make clean</span><br><span class="line"></span><br><span class="line"><span class="comment">## Allow users in the support group to restart specific services</span></span><br><span class="line">%support ALL=(root) /usr/sbin/service apache2 restart, /usr/sbin/service nginx restart</span><br></pre></td></tr></table></figure><p>上述配置说明不考虑注释行，含义如下：</p><ul><li>第1行表示允许 root 用户在所有主机上以任何用户身份执行任何命令。</li><li>第3行表示允许 johndoe 用户作为 root 用户以无需密码的方式执行 <code>/bin/ls</code> 和 <code>/usr/bin/apt-get</code> 命令。</li><li>第4行和第5行表示允许 johndoe 用户作为 appuser 用户或 backupuser 用户在特定目录下执行命令。</li><li>第6行表示允许 johndoe 用户作为 root 用户执行带有特定参数的 <code>/usr/sbin/usermod</code> 命令。</li><li>第7行表示禁止 banneduser 用户执行除 <code>/usr/bin/*</code> 之外的所有命令。</li><li>第8行表示允许 wheel 组的用户在 localhost 主机上以任何用户身份执行任何命令，且无需密码。</li><li>第9行表示允许 developer 组的用户执行带有特定参数的 <code>/usr/bin/git</code> 和 <code>/usr/bin/make</code> 命令。</li><li>第10行表示允许 support 组的用户作为 root 用户重启特定服务。</li></ul><h1 id="使用sudo用户"><a href="#使用sudo用户" class="headerlink" title="使用sudo用户"></a>使用sudo用户</h1><h2 id="创建用户并添加到-sudo-组"><a href="#创建用户并添加到-sudo-组" class="headerlink" title="创建用户并添加到 sudo 组"></a>创建用户并添加到 sudo 组</h2><p>在 Linux 上创建用户并将其添加到 <code>sudo</code> 组，可以按照以下步骤进行操作：</p><ol><li><p>打开终端：在操作系统中打开终端或命令行界面。</p></li><li><p>使用 <code>adduser</code> 命令创建新用户：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo adduser newuser</span><br></pre></td></tr></table></figure><p>这将提示你输入新用户的密码和其他详细信息。</p></li><li><p>将新用户添加到 <code>sudo</code> 组。可以使用 <code>usermod</code> 命令执行此操作：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo usermod -aG sudo newuser</span><br></pre></td></tr></table></figure><p>这将将新用户 <code>newuser</code> 添加到 <code>sudo</code> 组中。</p></li><li><p>验证用户是否成功添加到 <code>sudo</code> 组。你可以通过执行以下命令来检查用户所属的组：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">groups newuser</span><br></pre></td></tr></table></figure><p>如果 <code>sudo</code> 组在列表中显示，表示用户已成功添加到 <code>sudo</code> 组。</p></li></ol><p>现在，新创建的用户已经添加到 <code>sudo</code> 组，拥有 sudo 权限。他们可以使用 <code>sudo</code> 命令以管理员权限执行特权操作。请确保将适当的权限授予新用户，以确保系统的安全性和合规性。</p><h2 id="查看是否有sudo权限"><a href="#查看是否有sudo权限" class="headerlink" title="查看是否有sudo权限"></a>查看是否有sudo权限</h2><p>可以通过id命令查看指定用户是否在 <code>sudo</code> 组内，比如下面的用户就在27(sudo)这个组内：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@jenkins1604:~# id gyee</span><br><span class="line">uid=1004(gyee) gid=1004(gyee) groups=1004(gyee),27(sudo)</span><br></pre></td></tr></table></figure><p>也可以查看(sudo)组内是否包含当前用户：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@jenkins1604:~# getent group sudo</span><br><span class="line">sudo:x:27:admin,gyee</span><br></pre></td></tr></table></figure></p><p><code>getent</code> 是一个命令行工具，用于从系统数据库（如密码、组、主机、服务等）中获取条目的信息。它可以用于检索和显示与系统用户、组、网络服务和其他系统资源相关的信息 ，<code>getent</code> 命令的基本语法如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getent database entry</span><br></pre></td></tr></table></figure><p>其中，<code>database</code> 表示要查询的数据库，而 <code>entry</code> 则表示要获取的数据库条目。 以下是一些常见的 <code>getent</code> 命令示例：</p><ul><li><p>获取系统中所有用户的信息：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getent passwd</span><br></pre></td></tr></table></figure></li><li><p>获取特定用户的信息：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getent passwd &lt;username&gt;</span><br></pre></td></tr></table></figure></li><li><p>获取系统中所有组的信息：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getent group</span><br></pre></td></tr></table></figure></li><li><p>获取特定组的信息：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getent group &lt;groupname&gt;</span><br></pre></td></tr></table></figure></li><li><p>获取系统中所有网络服务的信息（/etc/services 文件中定义的服务）：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getent services</span><br></pre></td></tr></table></figure></li></ul><h2 id="没有sudo权限时执行会被记录"><a href="#没有sudo权限时执行会被记录" class="headerlink" title="没有sudo权限时执行会被记录"></a>没有sudo权限时执行会被记录</h2><p>当没有sudo权限时，执行命令会输出以下信息，显示此操作被记录在案：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">gyee@jenkins1604:/root$ sudo usermod -aG sudo gyee</span><br><span class="line">[sudo] password for gyee:</span><br><span class="line">gyee is not in the sudoers file.  This incident will be reported.</span><br></pre></td></tr></table></figure><p>可以查询系统安全日志</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@jenkins1604:~# cat /var/log/auth.log | grep gyee</span><br><span class="line">May 26 22:07:13 machine sudo:     gyee : user NOT in sudoers ; TTY=pts/1 ; PWD=/root ; USER=root ; COMMAND=/usr/sbin/usermod -aG sudo gyee</span><br></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li><code>sudo</code> 是一种在类 Unix 系统中使用的命令，用于以超级用户（superuser）的身份执行其他命令</li><li><code>root</code> 是超级用户，拥有对整个系统的完全控制权，<code>sudo</code> 是一个允许普通用户在需要时以超级用户的身份执行特定的命令</li><li><code>adduser</code> 和 <code>useradd</code> 都是在 Linux 系统上用于创建用户的命令，但 <code>adduser</code>命令更方便友好，尤其适用于交互式的用户创建</li><li><code>getent</code> 是一个命令行工具，用于从系统数据库（如密码、组、主机、服务等）中获取条目的信息</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/130569748" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>大家所谓的不满，是不满自己没成为人上人吧，至于温饱，很多人已经绰绰有余了~</p><p>2023-5-26 22:08:38</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;&lt;code&gt;sudo&lt;/code&gt;命令应该是任何一个服务器使用人员都绕不开的命令，比如那个大名鼎鼎的删库跑路命令 &lt;code&gt;sudo rm -rf /*&lt;/code&gt;，虽然经常使用，也知道用它能做一些普通用户无法完成的事情，那么它究竟是什么意思，有什么作用和好处，和超级无敌的 &lt;code&gt;root&lt;/code&gt; 用户又有什么关系呢？接下来我们来简单梳理一下。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/tags/linux/"/>
    
      <category term="服务器" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
      <category term="sudo" scheme="http://AlbertGithubHome.github.io/blog/tags/sudo/"/>
    
      <category term="adduser" scheme="http://AlbertGithubHome.github.io/blog/tags/adduser/"/>
    
      <category term="getent" scheme="http://AlbertGithubHome.github.io/blog/tags/getent/"/>
    
  </entry>
  
  <entry>
    <title>linux环境下使用daemon函数实现守护进程</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/05/12/linux%E7%8E%AF%E5%A2%83%E4%B8%8B%E4%BD%BF%E7%94%A8daemon%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/05/12/linux环境下使用daemon函数实现守护进程/</id>
    <published>2023-05-12T14:38:40.000Z</published>
    <updated>2023-06-18T07:27:04.083Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>daemon进程是后台守护进程，比如linux 下server都是daemon进程，像httpd、sshd等这些服务你肯定听说过，如果我们想写一个后台运行程序应该怎么做呢？其实非常容易，只要添加头文件 <code>#include &lt;unistd.h&gt;</code>，然后在调用 <code>daemon(1, 0)</code>函数即可。</p><a id="more"></a><h1 id="daemon函数说明"><a href="#daemon函数说明" class="headerlink" title="daemon函数说明"></a>daemon函数说明</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Put the program in the background, and dissociate from the controlling</span></span><br><span class="line"><span class="comment">   terminal.  If NOCHDIR is zero, do `chdir ("/")'.  If NOCLOSE is zero,</span></span><br><span class="line"><span class="comment">   redirects stdin, stdout, and stderr to /dev/null.  */</span></span><br><span class="line"><span class="function"><span class="keyword">extern</span> <span class="keyword">int</span> <span class="title">daemon</span> <span class="params">(<span class="keyword">int</span> __nochdir, <span class="keyword">int</span> __noclose)</span> __THROW __wur</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">/* Use misc.  */</span></span></span><br></pre></td></tr></table></figure><p>头文件中已经说得很清楚了，当 nochdir为零时，将当前目录变为根目录，否则不变，当 noclose为零时，标准输入、标准输出和错误输出重导向为/dev/null不输出任何信息，否则照样输出。</p><h1 id="daemon函数的实现"><a href="#daemon函数的实现" class="headerlink" title="daemon函数的实现"></a>daemon函数的实现</h1><p>关于找daemon函数的实现还是费了一些功夫的，我发现我根本不会在github上查代码，一开始以为是linux源码所以去翻了Linus Torvalds的Linux源码结果没找到，最后还是在google收到了一些，整理如下：</p><h2 id="安卓源码"><a href="#安卓源码" class="headerlink" title="安卓源码"></a>安卓源码</h2><blockquote><p><a href="https://android.googlesource.com/platform/bionic.git/+/donut-release/libc/unistd/daemon.c" target="_blank" rel="noopener">https://android.googlesource.com/platform/bionic.git/+/donut-release/libc/unistd/daemon.c</a></p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span>  <span class="title">daemon</span><span class="params">( <span class="keyword">int</span>  nochdir, <span class="keyword">int</span>  noclose )</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="keyword">pid_t</span>  pid;</span><br><span class="line">   <span class="keyword">if</span> ( !nochdir &amp;&amp; chdir(<span class="string">"/"</span>) != <span class="number">0</span> )</span><br><span class="line">       <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> ( !noclose )</span><br><span class="line">   &#123;</span><br><span class="line">     <span class="keyword">int</span>  fd = open(<span class="string">"/dev/null"</span>, O_RDWR);</span><br><span class="line">     <span class="keyword">if</span> ( fd &lt; <span class="number">0</span> )</span><br><span class="line">      <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">     <span class="keyword">if</span> ( dup2( fd, <span class="number">0</span> ) &lt; <span class="number">0</span> ||</span><br><span class="line">      dup2( fd, <span class="number">1</span> ) &lt; <span class="number">0</span> ||</span><br><span class="line">          dup2( fd, <span class="number">2</span> ) &lt; <span class="number">0</span> )</span><br><span class="line">     &#123;</span><br><span class="line">       close(fd);</span><br><span class="line">      <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">     &#125;</span><br><span class="line">     close(fd);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">   pid = fork();</span><br><span class="line">   <span class="keyword">if</span> (pid &lt; <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">   <span class="keyword">if</span> (pid &gt; <span class="number">0</span>)</span><br><span class="line">    _exit(<span class="number">0</span>);</span><br><span class="line">   <span class="keyword">if</span> ( setsid() &lt; <span class="number">0</span> )</span><br><span class="line">     <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">   <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="苹果源码"><a href="#苹果源码" class="headerlink" title="苹果源码"></a>苹果源码</h2><blockquote><p><a href="https://opensource.apple.com/source/Libc/Libc-167/gen.subproj/daemon.c.auto.html" target="_blank" rel="noopener">https://opensource.apple.com/source/Libc/Libc-167/gen.subproj/daemon.c.auto.html</a></p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;paths.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span></span><br><span class="line">daemon(nochdir, noclose)</span><br><span class="line">    <span class="keyword">int</span> nochdir, noclose;</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">int</span> fd;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span> (fork()) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">-1</span>:</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line">    <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        _exit(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (setsid() == <span class="number">-1</span>)</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!nochdir)</span><br><span class="line">        (<span class="keyword">void</span>)chdir(<span class="string">"/"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!noclose &amp;&amp; (fd = open(_PATH_DEVNULL, O_RDWR, <span class="number">0</span>)) != <span class="number">-1</span>) &#123;</span><br><span class="line">        (<span class="keyword">void</span>)dup2(fd, STDIN_FILENO);</span><br><span class="line">        (<span class="keyword">void</span>)dup2(fd, STDOUT_FILENO);</span><br><span class="line">        (<span class="keyword">void</span>)dup2(fd, STDERR_FILENO);</span><br><span class="line">        <span class="keyword">if</span> (fd &gt; <span class="number">2</span>)</span><br><span class="line">            (<span class="keyword">void</span>)close (fd);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> (<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="glibc源码"><a href="#glibc源码" class="headerlink" title="glibc源码"></a>glibc源码</h2><blockquote><p><a href="https://elixir.bootlin.com/glibc/glibc-2.37/source/misc/daemon.c" target="_blank" rel="noopener">https://elixir.bootlin.com/glibc/glibc-2.37/source/misc/daemon.c</a></p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(LIBC_SCCS) &amp;&amp; !defined(lint)</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">char</span> sccsid[] = <span class="string">"@(#)daemon.c    8.1 (Berkeley) 6/4/93"</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">/* LIBC_SCCS and not lint */</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;paths.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;device-nrs.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;not-cancel.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span></span><br><span class="line">daemon (<span class="keyword">int</span> nochdir, <span class="keyword">int</span> noclose)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">int</span> fd;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span> (__fork()) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">-1</span>:</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line">    <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        _exit(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (__setsid() == <span class="number">-1</span>)</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!nochdir)</span><br><span class="line">        (<span class="keyword">void</span>)__chdir(<span class="string">"/"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!noclose) &#123;</span><br><span class="line">        <span class="class"><span class="keyword">struct</span> __<span class="title">stat64_t64</span> <span class="title">st</span>;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> ((fd = __open_nocancel(_PATH_DEVNULL, O_RDWR, <span class="number">0</span>)) != <span class="number">-1</span></span><br><span class="line">            &amp;&amp; __glibc_likely (__fstat64_time64 (fd, &amp;st) == <span class="number">0</span>)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (__builtin_expect (S_ISCHR (st.st_mode), <span class="number">1</span>) != <span class="number">0</span></span><br><span class="line">#<span class="keyword">if</span> defined DEV_NULL_MAJOR &amp;&amp; defined DEV_NULL_MINOR</span><br><span class="line">                &amp;&amp; (st.st_rdev</span><br><span class="line">                == makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR))</span><br><span class="line">#endif</span><br><span class="line">                ) &#123;</span><br><span class="line">                (<span class="keyword">void</span>)__dup2(fd, STDIN_FILENO);</span><br><span class="line">                (<span class="keyword">void</span>)__dup2(fd, STDOUT_FILENO);</span><br><span class="line">                (<span class="keyword">void</span>)__dup2(fd, STDERR_FILENO);</span><br><span class="line">                <span class="keyword">if</span> (fd &gt; <span class="number">2</span>)</span><br><span class="line">                    (<span class="keyword">void</span>)__close (fd);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">/* We must set an errno value since no</span></span><br><span class="line"><span class="comment">                   function call actually failed.  */</span></span><br><span class="line">                __close_nocancel_nostatus (fd);</span><br><span class="line">                __set_errno (ENODEV);</span><br><span class="line">                <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            __close_nocancel_nostatus (fd);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> (<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="glibc-github地址"><a href="#glibc-github地址" class="headerlink" title="glibc github地址"></a>glibc github地址</h2><blockquote><p><a href="https://github.com/lattera/glibc/blob/master/misc/daemon.c" target="_blank" rel="noopener">https://github.com/lattera/glibc/blob/master/misc/daemon.c</a></p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(LIBC_SCCS) &amp;&amp; !defined(lint)</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">char</span> sccsid[] = <span class="string">"@(#)daemon.c    8.1 (Berkeley) 6/4/93"</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">/* LIBC_SCCS and not lint */</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;paths.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;device-nrs.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;not-cancel.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span></span><br><span class="line">daemon (<span class="keyword">int</span> nochdir, <span class="keyword">int</span> noclose)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">int</span> fd;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span> (__fork()) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">-1</span>:</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line">    <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        _exit(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (__setsid() == <span class="number">-1</span>)</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">-1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!nochdir)</span><br><span class="line">        (<span class="keyword">void</span>)__chdir(<span class="string">"/"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!noclose) &#123;</span><br><span class="line">        <span class="class"><span class="keyword">struct</span> <span class="title">stat64</span> <span class="title">st</span>;</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> ((fd = __open_nocancel(_PATH_DEVNULL, O_RDWR, <span class="number">0</span>)) != <span class="number">-1</span></span><br><span class="line">            &amp;&amp; (__builtin_expect (__fxstat64 (_STAT_VER, fd, &amp;st), <span class="number">0</span>)</span><br><span class="line">            == <span class="number">0</span>)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (__builtin_expect (S_ISCHR (st.st_mode), <span class="number">1</span>) != <span class="number">0</span></span><br><span class="line">#<span class="keyword">if</span> defined DEV_NULL_MAJOR &amp;&amp; defined DEV_NULL_MINOR</span><br><span class="line">                &amp;&amp; (st.st_rdev</span><br><span class="line">                == makedev (DEV_NULL_MAJOR, DEV_NULL_MINOR))</span><br><span class="line">#endif</span><br><span class="line">                ) &#123;</span><br><span class="line">                (<span class="keyword">void</span>)__dup2(fd, STDIN_FILENO);</span><br><span class="line">                (<span class="keyword">void</span>)__dup2(fd, STDOUT_FILENO);</span><br><span class="line">                (<span class="keyword">void</span>)__dup2(fd, STDERR_FILENO);</span><br><span class="line">                <span class="keyword">if</span> (fd &gt; <span class="number">2</span>)</span><br><span class="line">                    (<span class="keyword">void</span>)__close (fd);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">/* We must set an errno value since no</span></span><br><span class="line"><span class="comment">                   function call actually failed.  */</span></span><br><span class="line">                __close_nocancel_nostatus (fd);</span><br><span class="line">                __set_errno (ENODEV);</span><br><span class="line">                <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            __close_nocancel_nostatus (fd);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> (<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="挺有意思的写法"><a href="#挺有意思的写法" class="headerlink" title="挺有意思的写法"></a>挺有意思的写法</h2><blockquote><p><a href="https://github.com/bmc/daemonize/blob/master/daemon.c" target="_blank" rel="noopener">https://github.com/bmc/daemonize/blob/master/daemon.c</a></p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">daemon</span><span class="params">(<span class="keyword">int</span> nochdir, <span class="keyword">int</span> noclose)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">int</span> status = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    openlog(<span class="string">"daemonize"</span>, LOG_PID, LOG_DAEMON);</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Fork once to go into the background. */</span></span><br><span class="line">    <span class="keyword">if</span>((status = do_fork()) &lt; <span class="number">0</span> )</span><br><span class="line">        ;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Create new session */</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span>(setsid() &lt; <span class="number">0</span>)               <span class="comment">/* shouldn't fail */</span></span><br><span class="line">        status = <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Fork again to ensure that daemon never reacquires a control terminal. */</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span>((status = do_fork()) &lt; <span class="number">0</span> )</span><br><span class="line">        ;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">/* clear any inherited umask(2) value */</span></span><br><span class="line"></span><br><span class="line">        umask(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* We're there. */</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(! nochdir)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="comment">/* Go to a neutral corner. */</span></span><br><span class="line">            chdir(<span class="string">"/"</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(! noclose)</span><br><span class="line">            redirect_fds();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> status;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="nginx中实现"><a href="#nginx中实现" class="headerlink" title="nginx中实现"></a>nginx中实现</h2><blockquote><p><a href="https://github.com/nginx/nginx/blob/master/src/os/unix/ngx_daemon.c" target="_blank" rel="noopener">https://github.com/nginx/nginx/blob/master/src/os/unix/ngx_daemon.c</a></p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ngx_int_t</span></span><br><span class="line">ngx_daemon(<span class="keyword">ngx_log_t</span> *<span class="built_in">log</span>)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">int</span>  fd;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span> (fork()) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">-1</span>:</span><br><span class="line">        ngx_log_error(NGX_LOG_EMERG, <span class="built_in">log</span>, ngx_errno, <span class="string">"fork() failed"</span>);</span><br><span class="line">        <span class="keyword">return</span> NGX_ERROR;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ngx_parent = ngx_pid;</span><br><span class="line">    ngx_pid = ngx_getpid();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (setsid() == <span class="number">-1</span>) &#123;</span><br><span class="line">        ngx_log_error(NGX_LOG_EMERG, <span class="built_in">log</span>, ngx_errno, <span class="string">"setsid() failed"</span>);</span><br><span class="line">        <span class="keyword">return</span> NGX_ERROR;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    umask(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">    fd = open(<span class="string">"/dev/null"</span>, O_RDWR);</span><br><span class="line">    <span class="keyword">if</span> (fd == <span class="number">-1</span>) &#123;</span><br><span class="line">        ngx_log_error(NGX_LOG_EMERG, <span class="built_in">log</span>, ngx_errno,</span><br><span class="line">                      <span class="string">"open(\"/dev/null\") failed"</span>);</span><br><span class="line">        <span class="keyword">return</span> NGX_ERROR;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (dup2(fd, STDIN_FILENO) == <span class="number">-1</span>) &#123;</span><br><span class="line">        ngx_log_error(NGX_LOG_EMERG, <span class="built_in">log</span>, ngx_errno, <span class="string">"dup2(STDIN) failed"</span>);</span><br><span class="line">        <span class="keyword">return</span> NGX_ERROR;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (dup2(fd, STDOUT_FILENO) == <span class="number">-1</span>) &#123;</span><br><span class="line">        ngx_log_error(NGX_LOG_EMERG, <span class="built_in">log</span>, ngx_errno, <span class="string">"dup2(STDOUT) failed"</span>);</span><br><span class="line">        <span class="keyword">return</span> NGX_ERROR;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> 0</span></span><br><span class="line">    <span class="keyword">if</span> (dup2(fd, STDERR_FILENO) == <span class="number">-1</span>) &#123;</span><br><span class="line">        ngx_log_error(NGX_LOG_EMERG, <span class="built_in">log</span>, ngx_errno, <span class="string">"dup2(STDERR) failed"</span>);</span><br><span class="line">        <span class="keyword">return</span> NGX_ERROR;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (fd &gt; STDERR_FILENO) &#123;</span><br><span class="line">        <span class="keyword">if</span> (close(fd) == <span class="number">-1</span>) &#123;</span><br><span class="line">            ngx_log_error(NGX_LOG_EMERG, <span class="built_in">log</span>, ngx_errno, <span class="string">"close() failed"</span>);</span><br><span class="line">            <span class="keyword">return</span> NGX_ERROR;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> NGX_OK;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="chatgpt生成"><a href="#chatgpt生成" class="headerlink" title="chatgpt生成"></a>chatgpt生成</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">daemon</span><span class="params">(<span class="keyword">int</span> nochdir, <span class="keyword">int</span> noclose)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">pid_t</span> pid;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建子进程并退出父进程</span></span><br><span class="line">    <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建新的会话</span></span><br><span class="line">    <span class="keyword">if</span> (setsid() &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 忽略 SIGHUP 信号</span></span><br><span class="line">    signal(SIGHUP, SIG_IGN);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 再次创建子进程并退出父进程</span></span><br><span class="line">    <span class="keyword">if</span> ((pid = fork()) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (pid != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更改当前工作目录</span></span><br><span class="line">    <span class="keyword">if</span> (!nochdir) &#123;</span><br><span class="line">        <span class="keyword">if</span> (chdir(<span class="string">"/"</span>) &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 关闭文件描述符</span></span><br><span class="line">    <span class="keyword">if</span> (!noclose) &#123;</span><br><span class="line">        <span class="keyword">int</span> fd;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> ((fd = open(<span class="string">"/dev/null"</span>, O_RDWR, <span class="number">0</span>)) != <span class="number">-1</span>) &#123;</span><br><span class="line">            dup2(fd, STDIN_FILENO);</span><br><span class="line">            dup2(fd, STDOUT_FILENO);</span><br><span class="line">            dup2(fd, STDERR_FILENO);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (fd &gt; STDERR_FILENO) &#123;</span><br><span class="line">                close(fd);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="daemon函数fork两次的原因"><a href="#daemon函数fork两次的原因" class="headerlink" title="daemon函数fork两次的原因"></a>daemon函数fork两次的原因</h1><p>以上这些开源实现中，有的fork一次，有的fork两次，不过fork一次的多一些，对于这个问题我还查了不少资料</p><blockquote><p><a href="http://www.cppblog.com/justin-shi/archive/2014/10/10/208530.html" target="_blank" rel="noopener">http://www.cppblog.com/justin-shi/archive/2014/10/10/208530.html</a><br><a href="https://blog.csdn.net/shaoye_csdn1/article/details/94599271" target="_blank" rel="noopener">https://blog.csdn.net/shaoye_csdn1/article/details/94599271</a><br><a href="https://m.xp.cn/b.php/75000.html" target="_blank" rel="noopener">https://m.xp.cn/b.php/75000.html</a></p></blockquote><p>关于为什么要fork两次，我找到了上面几篇相关的解释，总结如下：</p><p>第一次fork使父进程终止，让子进程中进入后台执行，同时保证了会话id与当前子进程id不同，可以调用setsid函数创建新的会话，保证了子进程是会话组长(sid==pid)，也是进程组组长(pgid == pid)。</p><p>第二次fork目的是防止进程再次打开一个控制终端，因为打开一个控制终端的前提条件是该进程必须是会话组长，再fork一次可以保证子进程pid != sid（sid是进程父进程的sid），所以也无法打开新的控制终端。</p><p><strong><em>关于第一次fork我是理解的，第二次fork我也做过实验，无论fork一次还是两次，都可以再次打开新的终端，也就是说即使保证了pid != sid，一样可以打开新的终端，而有很多daemon函数的开源的实现中也只是调用了一次fork，包括我使用的Ubuntu20.04实验结果也是调用了一次fork，所以调用两次fork的原因成谜。</em></strong></p><blockquote><p><a href="https://www.cntofu.com/book/46/linux_system/153.md" target="_blank" rel="noopener">https://www.cntofu.com/book/46/linux_system/153.md</a></p></blockquote><p>而这篇文章总结了两次fork技巧，不过和我们本文中所说的daemon两次fork前提不一样，在父进程不退出时会使用两次fork技巧避免出现僵尸进程，但时在daemon函数中fork只之后的父进程都是直接退出的，所以应该不是这个原因。</p><p>关于这个疑问我还专门去ChatGPT上问了一下，它给的答案如下：</p><blockquote><p>在早期的Unix系统中，使用 <code>daemon()</code> 函数通常需要调用两次 <code>fork()</code> 函数，以确保子进程成为孤儿进程（orphan process），即与任何终端无关联，并且不会收到终端信号。孤儿进程通常是由父进程创建的子进程，但父进程已经退出或被杀死。</p><p>以下是调用两次 <code>fork()</code> 函数的工作原理：</p><ol><li>调用第一次 <code>fork()</code> 函数：创建子进程1。父进程退出，子进程1成为孤儿进程，也就是没有父进程的进程。此时，子进程1仍然与原来的控制终端相关联。</li><li>子进程1调用 <code>setsid()</code> 函数创建一个新的会话，并成为新会话的领头进程和新进程组的组长进程。由于孤儿进程已经成为新会话的领头进程，因此它无法再重新关联到终端。此时，子进程1仍然是当前进程组的组长进程，并且与新的控制终端相关联。</li><li><p>调用第二次 <code>fork()</code> 函数：创建子进程2。由于子进程1是新会话的领头进程，因此子进程2不再与任何控制终端相关联。此时，子进程2成为孤儿进程，并与新会话和新进程组分离。</p></li><li><p>子进程2关闭不需要的文件描述符，改变工作目录和文件掩码，最后运行守护进程。</p></li></ol></blockquote><p>&gt;</p><blockquote><p>需要注意的是，在现代的Linux系统中，使用 <code>daemon()</code> 函数只需要调用一次 <code>fork()</code> 函数和一些必要的操作（如 <code>setsid()</code>、关闭文件描述符等），就可以将进程变成守护进程，而不需要调用两次 <code>fork()</code> 函数。</p></blockquote><h1 id="自己做实验的代码"><a href="#自己做实验的代码" class="headerlink" title="自己做实验的代码"></a>自己做实验的代码</h1><p>自己在测试 <code>daemon</code> 函数的时候写了一些代码，记录一下方便再次拿来用，请原谅我c/c++混用，为了验证某个问题我已经各种尝试了。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;limits.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;fcntl.h&gt; //open O_RDWR</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/stat.h&gt; //umask</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;iostream&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">my_daemon</span> <span class="params">(<span class="keyword">int</span> nochdir, <span class="keyword">int</span> noclose)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="keyword">pid_t</span> pid;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/* Fork and exit parent process.  */</span></span><br><span class="line">  <span class="keyword">if</span> ((pid = fork ()) &lt; <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> (pid != <span class="number">0</span>)</span><br><span class="line">    _exit (<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">/* Detach ourselves from the parent process group.  */</span></span><br><span class="line">  <span class="keyword">if</span> (setsid () &lt; <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/* Fork again to ensure we are not session leader.  */</span></span><br><span class="line">  <span class="keyword">if</span> ((pid = fork ()) &lt; <span class="number">0</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> (pid != <span class="number">0</span>)</span><br><span class="line">    _exit (<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">/* Change the file mode mask.  */</span></span><br><span class="line">  umask (<span class="number">022</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">/* If nochdir is zero, change to the root directory.  */</span></span><br><span class="line">  <span class="keyword">if</span> (!nochdir)</span><br><span class="line">    chdir (<span class="string">"/"</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">/* If noclose is zero, redirect standard input, standard output,</span></span><br><span class="line"><span class="comment">     and standard error to /dev/null.  */</span></span><br><span class="line">  <span class="keyword">if</span> (!noclose)</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="keyword">int</span> fd;</span><br><span class="line"></span><br><span class="line">      fd = open (<span class="string">"/dev/null"</span>, O_RDWR, <span class="number">0</span>);</span><br><span class="line">      <span class="keyword">if</span> (fd != <span class="number">-1</span>)</span><br><span class="line">        &#123;</span><br><span class="line">          dup2 (fd, STDIN_FILENO);</span><br><span class="line">          dup2 (fd, STDOUT_FILENO);</span><br><span class="line">          dup2 (fd, STDERR_FILENO);</span><br><span class="line">          <span class="keyword">if</span> (fd &gt; <span class="number">2</span>)</span><br><span class="line">            close (fd);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">showpidinfo</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span>* info)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">std</span>::<span class="built_in">cout</span> &lt;&lt; info &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"getpid="</span> &lt;&lt; getpid() &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"getppid="</span> &lt;&lt; getppid() &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"getpgrp="</span> &lt;&lt; getpgrp() &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"getpgid(pid)="</span> &lt;&lt; getpgid(getpid()) &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"getsid(pid)="</span> &lt;&lt; getsid(getpid()) &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"getuid="</span> &lt;&lt; getuid() &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"geteuid="</span> &lt;&lt; geteuid() &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"getgid="</span> &lt;&lt; getgid() &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"getegid="</span> &lt;&lt; getegid() &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="string">"ttyname(0)="</span> &lt;&lt; ttyname(<span class="number">0</span>) &lt;&lt; <span class="string">"\n"</span></span><br><span class="line">        &lt;&lt; <span class="built_in">std</span>::<span class="built_in">endl</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">open_new_terminal</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    system(<span class="string">"gnome-terminal -- /bin/bash -c 'date; exec bash'"</span>);</span><br><span class="line">    <span class="comment">//system("konsole  -e './v'");</span></span><br><span class="line">    <span class="comment">//system("xterm   -e './v'");</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> *argv[])</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">char</span> strCurPath[PATH_MAX];</span><br><span class="line"></span><br><span class="line">    showpidinfo(<span class="string">"===before daemon:"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//while(1);</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">int</span> n = daemon(<span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line">    <span class="comment">//int n = my_daemon(0, 1);</span></span><br><span class="line"></span><br><span class="line">    open_new_terminal();</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"daemon result %d\n"</span>, n);</span><br><span class="line">    showpidinfo(<span class="string">"===after daemon:"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(n &lt; <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        perror(<span class="string">"error daemon.../n"</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    sleep(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(getcwd(strCurPath, PATH_MAX) == <span class="literal">NULL</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        perror(<span class="string">"error getcwd"</span>);</span><br><span class="line">        <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">"cur path [%s]\n"</span>, strCurPath);  <span class="comment">//打印当前目录</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>showpidinfo</code> 函数用于输出进程id相关信息</li><li><code>open_new_terminal</code> 函数用于打开一个新的控制台</li><li><code>my_daemon</code> 函数是自定义的daemon实现，采用fork两次的形式</li><li><code>perror</code> 可以输出详细的报错信息</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># demo @ ThinkPad-X390 in ~/cpp/daemontest [23:30:53]</span></span><br><span class="line">$ g++ a.cpp</span><br><span class="line">$ ./a.out</span><br><span class="line">===before daemon:</span><br><span class="line">getpid=352638</span><br><span class="line">getppid=62351</span><br><span class="line">getpgrp=352638</span><br><span class="line">getpgid(pid)=352638</span><br><span class="line">getsid(pid)=62351</span><br><span class="line">getuid=1000</span><br><span class="line">geteuid=1000</span><br><span class="line">getgid=1000</span><br><span class="line">getegid=1000</span><br><span class="line">ttyname(0)=/dev/pts/15</span><br><span class="line"><span class="comment"># demo @ ThinkPad-X390 in ~/cpp/daemontest [23:30:57]</span></span><br><span class="line">$ Unable to init server: Could not connect: Connection refused</span><br><span class="line"><span class="comment"># Failed to parse arguments: Cannot open display:</span></span><br><span class="line">daemon result 0</span><br><span class="line">===after daemon:</span><br><span class="line">getpid=352639</span><br><span class="line">getppid=1</span><br><span class="line">getpgrp=352639</span><br><span class="line">getpgid(pid)=352639</span><br><span class="line">getsid(pid)=352639</span><br><span class="line">getuid=1000</span><br><span class="line">geteuid=1000</span><br><span class="line">getgid=1000</span><br><span class="line">getegid=1000</span><br><span class="line">ttyname(0)=/dev/pts/15</span><br><span class="line"></span><br><span class="line">cur path [/]</span><br></pre></td></tr></table></figure><p>试验结果不论是fork一次还是两次，都可以打开新的控制台，这与网络上很多文章是相悖的，真相还需继续检验。运行结果中的报错”Unable to init server: Could not connect: Connection refused” 是因为我远程连接无法打开GUI。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>想实现后台守护进程，只需添加头文件 <code>#include &lt;unistd.h&gt;</code>，然后在调用 <code>daemon(1, 0)</code>函数即可</li><li><code>daemon</code> 的开源实现有很多种，早期很多说是fork两次，但是查了很多最新的版本都是fork一次</li><li>c/c++代码中打开控制台可以使用 <code>system(&quot;gnome-terminal -- /bin/bash -c &#39;date; exec bash&#39;&quot;);</code> 前提是可以正常打开用户界面的电脑，远程操作是不可以的</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/130395473" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>除了死亡，都是擦伤~</p><p>2023-5-12 22:38:40</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;daemon进程是后台守护进程，比如linux 下server都是daemon进程，像httpd、sshd等这些服务你肯定听说过，如果我们想写一个后台运行程序应该怎么做呢？其实非常容易，只要添加头文件 &lt;code&gt;#include &amp;lt;unistd.h&amp;gt;&lt;/code&gt;，然后在调用 &lt;code&gt;daemon(1, 0)&lt;/code&gt;函数即可。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/tags/linux/"/>
    
      <category term="服务器" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
      <category term="daemon" scheme="http://AlbertGithubHome.github.io/blog/tags/daemon/"/>
    
      <category term="gnome-terminal" scheme="http://AlbertGithubHome.github.io/blog/tags/gnome-terminal/"/>
    
  </entry>
  
  <entry>
    <title>linux下使用dd命令测试磁盘的读写速度</title>
    <link href="http://AlbertGithubHome.github.io/blog/2023/04/26/linux%E4%B8%8B%E4%BD%BF%E7%94%A8dd%E5%91%BD%E4%BB%A4%E6%B5%8B%E8%AF%95%E7%A3%81%E7%9B%98%E7%9A%84%E8%AF%BB%E5%86%99%E9%80%9F%E5%BA%A6/"/>
    <id>http://AlbertGithubHome.github.io/blog/2023/04/26/linux下使用dd命令测试磁盘的读写速度/</id>
    <published>2023-04-26T14:28:10.000Z</published>
    <updated>2023-06-18T07:31:13.931Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近打包更新总是特别慢，其中有一步使用<code>md5sum</code>命令计算MD5值的操作有时居然要卡一个小时，虽说计算MD5比较耗时，但通常计算5个G数据的md5值也就1分多钟，卡一个多小时有点说不过去了，所以在卡顿时使用 <code>top</code> 命令查看了进程情况，发现md5sum进程在卡顿时总是处于 <code>D</code> 状态，这个还是第一次遇到，所以查了些资料记录一下。</p><a id="more"></a><h1 id="D状态进程"><a href="#D状态进程" class="headerlink" title="D状态进程"></a>D状态进程</h1><p>D 状态进程(TASK_UNINTERRUPTIBLE)，处于不可中断的睡眠状态，指进程不响应异步信号。<br>绝大多数情况下，进程处在睡眠状态时，总是应该能够响应异步信号的。而D状态存在的意义就在于，内核的某些处理流程是不能被打断，比如进程调用read系统调用对某个设备文件进行读操作，一般需要使用D状态对进程进行保护，以避免进程与设备交互的过程被打断，造成设备陷入不可控的状态。这种情况下的D状态总是非常短暂的，通过ps命令基本上不可能捕捉到。</p><p><strong>但是我捉到了，在执行md5sum命令时经常捉到，D状态往往是由于 I/O 资源得不到满足，而引发等待~</strong></p><p>这是个关键点，既然IO得不到满足就查一下IO，计算md5也就从磁盘上读一些文件，所以怀疑问题出现在磁盘的读写上，进而想测一些磁盘的读写速度，这就用到了今天想说的 <code>dd</code> 命令。</p><h1 id="dd命令"><a href="#dd命令" class="headerlink" title="dd命令"></a>dd命令</h1><p>磁盘的读写速度需要通过实际读写文件来测试，<code>dd</code> 命令就可以完成这种测试，配合 <code>time</code> 命令可以统计命令执行时间</p><ul><li><p>仅测读取速度</p>  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># time dd if=/tmp/test of=/dev/null bs=16k</span></span><br><span class="line">1000000+0 records <span class="keyword">in</span></span><br><span class="line">1000000+0 records out</span><br><span class="line">16384000000 bytes (16 GB) copied, 42.7044 s, 384 MB/s</span><br><span class="line"></span><br><span class="line">real    0m42.718s</span><br><span class="line">user    0m0.387s</span><br><span class="line">sys     0m8.951s</span><br></pre></td></tr></table></figure></li><li><p>仅测写入速度</p>  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># time dd if=/dev/zero of=/tmp/test bs=16k count=1000000</span></span><br><span class="line">1000000+0 records <span class="keyword">in</span></span><br><span class="line">1000000+0 records out</span><br><span class="line">16384000000 bytes (16 GB) copied, 35.7006 s, 459 MB/s</span><br><span class="line"></span><br><span class="line">real    0m38.428s</span><br><span class="line">user    0m0.356s</span><br><span class="line">sys     0m12.608s</span><br></pre></td></tr></table></figure></li><li><p>测试读写速度</p>  <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># time dd if=/tmp/test of=/tmp/test2 bs=16k</span></span><br><span class="line">1000000+0 records <span class="keyword">in</span></span><br><span class="line">1000000+0 records out</span><br><span class="line">16384000000 bytes (16 GB) copied, 87.3214 s, 188 MB/s</span><br><span class="line"></span><br><span class="line">real    1m27.331s</span><br><span class="line">user    0m0.430s</span><br><span class="line">sys     0m15.799s</span><br></pre></td></tr></table></figure></li></ul><h1 id="问题原因"><a href="#问题原因" class="headerlink" title="问题原因"></a>问题原因</h1><p>最后测试发现硬盘读写速度不是很稳定，找运维沟通发现这台服务器使用的网络硬盘，存储带宽有限，当执行太多磁盘任务时，存储带宽会被占满，会造成硬盘读写缓慢，进而影响了之前提到的计算MD5的操作步骤，换了块本地硬盘就好了。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>进程显示D状态时处于不可中断的睡眠状态，往是由于 I/O 资源得不到满足而引发了等待</li><li>仅测试写入速度 <code>time dd if=/dev/zero of=/tmp/test bs=16k count=1000000</code></li><li>仅测试读取速度 <code>time dd if=/tmp/test of=/dev/null bs=16k</code></li><li>测试读写速度 <code>time dd if=/tmp/test of=/tmp/test2 bs=16k</code></li><li><code>/dev/zero</code> 是一个特殊的文件，当你读它的时候会得到无限的空字符，可用来初始化文件，不产生IO</li><li><code>/dev/null</code> 也是一个特殊的文件，它丢弃一切写入其中的数据，被称为黑洞，也不产生IO</li><li>网络硬盘受存储带宽的影响，一旦带宽被打满，硬盘读写速度也就慢了</li></ul><center><a href="https://blog.csdn.net/albertsh/article/details/130353739" target="_blank" rel="noopener"> ==&gt;&gt; 反爬链接，请勿点击，原地爆炸，概不负责！&lt;&lt;== </a></center><hr><blockquote><p>好像遥不可及，但突然有那么一瞬间又感觉触手可及，貌似也没什么退路，只有往前冲了</p><p>2023-4-26 22:28:10</p></blockquote>]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;前言&quot;&gt;&lt;a href=&quot;#前言&quot; class=&quot;headerlink&quot; title=&quot;前言&quot;&gt;&lt;/a&gt;前言&lt;/h1&gt;&lt;p&gt;最近打包更新总是特别慢，其中有一步使用&lt;code&gt;md5sum&lt;/code&gt;命令计算MD5值的操作有时居然要卡一个小时，虽说计算MD5比较耗时，但通常计算5个G数据的md5值也就1分多钟，卡一个多小时有点说不过去了，所以在卡顿时使用 &lt;code&gt;top&lt;/code&gt; 命令查看了进程情况，发现md5sum进程在卡顿时总是处于 &lt;code&gt;D&lt;/code&gt; 状态，这个还是第一次遇到，所以查了些资料记录一下。&lt;/p&gt;
    
    </summary>
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/categories/linux/"/>
    
    
      <category term="linux" scheme="http://AlbertGithubHome.github.io/blog/tags/linux/"/>
    
      <category term="运维" scheme="http://AlbertGithubHome.github.io/blog/tags/%E8%BF%90%E7%BB%B4/"/>
    
      <category term="服务器" scheme="http://AlbertGithubHome.github.io/blog/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/"/>
    
      <category term="硬盘读写" scheme="http://AlbertGithubHome.github.io/blog/tags/%E7%A1%AC%E7%9B%98%E8%AF%BB%E5%86%99/"/>
    
      <category term="dd" scheme="http://AlbertGithubHome.github.io/blog/tags/dd/"/>
    
  </entry>
  
</feed>
