﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-markqian86-随笔分类-编程技巧随笔</title><link>http://www.cppblog.com/markqian86/category/21283.html</link><description>practice makes perfect，阅读、分析、练习、总结。</description><language>zh-cn</language><lastBuildDate>Wed, 20 Jan 2021 07:56:04 GMT</lastBuildDate><pubDate>Wed, 20 Jan 2021 07:56:04 GMT</pubDate><ttl>60</ttl><item><title>Linux Makefile 生成 *.d 依赖文件以及 gcc -M -MF -MP 等相关选项说明</title><link>http://www.cppblog.com/markqian86/archive/2020/12/29/217553.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Tue, 29 Dec 2020 11:24:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/12/29/217553.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217553.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/12/29/217553.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217553.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217553.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1. 为什么要使用后缀名为 .d 的依赖文件？在 Makefile 中， 目标文件的依赖关系需要包含一系列的头文件。比如main.c 源文件内容如下：#include "stdio.h" #include "defs.h"  int main(int argc, char *argv[]) { 	printf("Hello, %s!\n", NAME); 	return 0; } 		 12345...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2020/12/29/217553.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/217553.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-12-29 19:24 <a href="http://www.cppblog.com/markqian86/archive/2020/12/29/217553.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>.NET Core 3 在 CentOS 7 x64 下部署</title><link>http://www.cppblog.com/markqian86/archive/2020/12/28/217552.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Mon, 28 Dec 2020 09:45:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/12/28/217552.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217552.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/12/28/217552.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217552.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217552.html</trackback:ping><description><![CDATA[<div><div><h4>1. 在 CentOS 7 上安装 .NET Core 运行时</h4> <ul> <li>在安装. net之前，您需要注册Microsoft密钥、注册产品存储库并安装所需的依赖项。这只需要在每台机器上执行一次。</li> </ul> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-ruby"=""><code language-ruby"="">sudo rpm <span operator"="">-</span><span constant"="">Uvh</span> https<span punctuation"="">:</span><span operator"="">/</span><span operator"="">/</span>packages<span punctuation"="">.</span>microsoft<span punctuation"="">.</span>com<span operator"="">/</span>config<span operator"="">/</span>centos<span operator"="">/</span><span number"="">7</span><span operator"="">/</span>packages<span operator"="">-</span>microsoft<span operator"="">-</span>prod<span punctuation"="">.</span>rpm </code></pre></div> <ul> <li>安装运行时</li> </ul> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-css"=""><code language-css"="">sudo yum update sudo yum install aspnetcore-runtime-3.0 </code></pre></div> <blockquote> <p>如果要安装 .NET SDK，使用 <code>sudo yum install dotnet-sdk-3.0</code> 这个命令。</p> </blockquote> <ul> <li>检查是否正确安装</li> </ul> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-undefined"=""><code language-undefined"="">dotnet </code></pre></div> <blockquote> <p>如果运行该命令，打印出关于如何使用dotnet的信息，就可以开始了。</p> </blockquote> <h4>2. 准备一个 .NET Core 3 的 Web 应用</h4> <p>编辑一个 .NET Core 3 的 Web 应用</p> <h4>3. 发布程序</h4> <p>右键项目，选择发布，一步一步操作即可，目标运行时选择 linux-x64。</p> <blockquote> <p>使用的 VS 2019</p> </blockquote> <h4>4. 发布的程序上传到 CentOS 服务器上</h4> <ol> <li>cd 到指定目录</li> <li>运行 dotnet 项目.dll 或者 nohup dotnet 项目.dll</li> <li>访问网站，能够看到网站已经能够访问</li> </ol> <blockquote> <p>查看防火墙状态：systemctl status firewalld <br /><br /> 查询指定端口是否开启：firewalld-cmd --query-port=80/tcp <br /><br /> 开启防火墙：systemctl start firewalld <br /><br /> 关闭防火墙：systemctl stop firewalld</p> </blockquote> <h4>5. 安装 Nginx</h4> <h4>6. 配置 Nginx 代理</h4> <ol> <li>定位到 nginx 配置文件目录</li> </ol> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-bash"=""><code language-bash"="">cd /etc/nginx/conf.d/ </code></pre></div> <ol start="2"> <li>编辑配置文件</li> </ol> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-cpp"=""><code language-cpp"="">vim <span keyword"="">default</span><span punctuation"="">.</span>conf </code></pre></div> <p>如下</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-cpp"=""><code language-cpp"="">   location <span operator"="">/</span> <span punctuation"="">{</span>         proxy_pass http<span operator"="">:</span><span operator"="">/</span><span operator"="">/</span>localhost<span operator"="">:</span><span number"="">5000</span><span punctuation"="">;</span>     <span punctuation"="">}</span> </code></pre></div> <p>将请求映射到本地请求的 5000 端口上。</p> <ol start="3"> <li>如果报错 13: Permission denied while connecting to upstream 之类的错误，参考：<a href="https://links.jianshu.com/go?to=https%3A%2F%2Fwww.cnblogs.com%2Fsongxingzhu%2Fp%2F10063043.html" target="_blank">https://www.cnblogs.com/songxingzhu/p/10063043.html</a> </li> </ol> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-undefined"=""><code language-undefined"="">setsebool -P httpd_can_network_connect 1 </code></pre></div> <h4>7. 安装 Supervisor</h4> <h4>8. 使用 Supervisor 配置守护进程</h4> <p>在 <code>/etc/supervisor/conf.d/</code> 下创建 sampleDemo.ini 文件</p> <p>sampleDemo.ini 配置文件如下</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-ruby"=""><code language-ruby"=""><span punctuation"="">[</span>program<span symbol"="">:SampleDemo</span><span punctuation"="">]</span>   <span punctuation"="">;</span>程序名称 command<span operator"="">=</span>dotnet <span constant"="">WebUI</span><span punctuation"="">.</span>dll <span punctuation"="">;</span>需要执行的命令 directory<span operator"="">=</span><span operator"="">/</span>usr<span operator"="">/</span>local<span operator"="">/</span>dotnetCore3<span operator"="">/</span>sampleDemo <span punctuation"="">;</span>命令执行的目录 environment<span operator"="">=</span><span constant"="">ASPNETCORE__ENVIRONMENT</span><span operator"="">=</span><span constant"="">Production</span> <span punctuation"="">;</span>环境变量 user<span operator"="">=</span>root <span punctuation"="">;</span>用户 stopsignal<span operator"="">=</span><span constant"="">INT</span> <span punctuation"="">;</span>当请求停止时用来终止程序的信号。这可以是任何术语，<span constant"="">HUP</span><span punctuation"="">,</span> <span constant"="">INT</span><span punctuation"="">,</span> <span constant"="">QUIT</span><span punctuation"="">,</span> <span constant"="">KILL</span><span punctuation"="">,</span> <span constant"="">USR1</span>或<span constant"="">USR2</span>。 autostart<span operator"="">=</span><span keyword"="">true</span> <span punctuation"="">;</span>是否自启动 autorestart<span operator"="">=</span><span keyword"="">true</span> <span punctuation"="">;</span>是否自动重启 startsecs<span operator"="">=</span><span number"="">10</span> <span punctuation"="">;</span>自动重启时间间隔（s） stderr_logfile<span operator"="">=</span><span operator"="">/</span>var<span operator"="">/</span>log<span operator"="">/</span>sampleDemo<span operator"="">/</span>err<span punctuation"="">.</span>log <span punctuation"="">;</span>错误日志文件 stdout_logfile<span operator"="">=</span><span operator"="">/</span>var<span operator"="">/</span>log<span operator"="">/</span>sampleDemo<span operator"="">/</span>out<span punctuation"="">.</span>log <span punctuation"="">;</span>输出日志文件 </code></pre></div> <p><a href="https://links.jianshu.com/go?to=http%3A%2F%2Fwww.supervisord.org%2Fconfiguration.html%23program-x-section-settings" target="_blank">http://www.supervisord.org/configuration.html#program-x-section-settings</a></p> <blockquote> <p>重启：supervisorctl reload</p> </blockquote> <p>访问 Supervisor Web 管理器，可以实现对部署程序的启动、停止、重启等操作。</p></div><br /><br />作者：一青叶<br />链接：https://www.jianshu.com/p/88662513c69b<br />来源：简书<br />著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</div><img src ="http://www.cppblog.com/markqian86/aggbug/217552.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-12-28 17:45 <a href="http://www.cppblog.com/markqian86/archive/2020/12/28/217552.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>p4 linux下切换工作区</title><link>http://www.cppblog.com/markqian86/archive/2020/11/25/217516.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Wed, 25 Nov 2020 07:19:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/11/25/217516.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217516.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/11/25/217516.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217516.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217516.html</trackback:ping><description><![CDATA[1、p4 client my_workspace<br />&nbsp; &nbsp;修改view，配置myclient<br /><br />2、/data/mydir/ 目录下创建 .p4config文件，添加内容：<br /><br /><div>P4USER=myuser</div><div>P4CLIENT=myclient<br /><br />3、vim ~/.bashrc, 添加<br /><br /><div>export P4USER=myuser</div><div>export P4PORT=10.8.7.9:666</div><div>export P4CONFIG=.p4config</div><div>export P4IGNORE=.p4ignore</div><br />4、source ~/.bashrc<br /><br />5、p4 set<br /><br />6、p4 sync ./...<br /></div><img src ="http://www.cppblog.com/markqian86/aggbug/217516.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-11-25 15:19 <a href="http://www.cppblog.com/markqian86/archive/2020/11/25/217516.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>跟踪进程的所有系统调用</title><link>http://www.cppblog.com/markqian86/archive/2020/10/29/217493.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 29 Oct 2020 08:52:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/10/29/217493.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217493.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/10/29/217493.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217493.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217493.html</trackback:ping><description><![CDATA[<pre style="box-sizing: border-box; font-family: Consolas, &quot;Andale Mono WT&quot;, &quot;Andale Mono&quot;, &quot;Lucida Console&quot;, &quot;Lucida Sans Typewriter&quot;, &quot;DejaVu Sans Mono&quot;, &quot;Bitstream Vera Sans Mono&quot;, &quot;Liberation Mono&quot;, &quot;Nimbus Mono L&quot;, Monaco, &quot;Courier New&quot;, Courier, monospace; font-size: 12px; margin-top: 0px; margin-bottom: 0px; padding: 12px; overflow: auto; line-height: normal; color: #3f4549; background-color: #fcfcfc;"><span style="box-sizing: border-box;">strace</span> <span style="box-sizing: border-box;">-</span><span style="box-sizing: border-box;">o</span> <span style="box-sizing: border-box;">output</span><span style="box-sizing: border-box;">.</span><span style="box-sizing: border-box;">txt</span> -c <span style="box-sizing: border-box;">-</span><span style="box-sizing: border-box;">T</span> <span style="box-sizing: border-box;">-</span><span style="box-sizing: border-box;">tt</span> <span style="box-sizing: border-box;">-</span><span style="box-sizing: border-box;">e</span> <span style="box-sizing: border-box;">trace</span><span style="box-sizing: border-box;">=</span><span style="box-sizing: border-box;">all</span> <span style="box-sizing: border-box;">-</span><span style="box-sizing: border-box;">p</span> <span style="box-sizing: border-box;">28979</span></pre><img src ="http://www.cppblog.com/markqian86/aggbug/217493.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-10-29 16:52 <a href="http://www.cppblog.com/markqian86/archive/2020/10/29/217493.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>文件内容查询shell脚本</title><link>http://www.cppblog.com/markqian86/archive/2020/10/15/217483.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 15 Oct 2020 07:40:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/10/15/217483.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217483.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/10/15/217483.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217483.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217483.html</trackback:ping><description><![CDATA[<div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->#!/bin/bash<br />WORK_DIR=`pwd`<br />FUN_NAME=$1<br />find&nbsp;$WORK_DIR&nbsp;-name&nbsp;"*.h"&nbsp;-o&nbsp;-name&nbsp;"*.cpp"&nbsp;-o&nbsp;-name&nbsp;"*.c"&nbsp;|&nbsp;xargs&nbsp;grep -n $FUN_NAME</div><img src ="http://www.cppblog.com/markqian86/aggbug/217483.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-10-15 15:40 <a href="http://www.cppblog.com/markqian86/archive/2020/10/15/217483.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>动态库中protobuf版本冲突</title><link>http://www.cppblog.com/markqian86/archive/2020/10/14/217481.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Wed, 14 Oct 2020 09:18:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/10/14/217481.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217481.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/10/14/217481.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217481.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217481.html</trackback:ping><description><![CDATA[1、描述<br />
接入sdk, libshared.so, 编译时包含了libprotobuf.a, 但本地使用的brpc库，libbrpc.so编译依赖本地libprotobuf.so， 导致冲突<br />
<div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%; word-break: break-all;"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->[libprotobuf&nbsp;FATAL&nbsp;google/protobuf/stubs/common.cc:79]&nbsp;This&nbsp;program&nbsp;was&nbsp;compiled&nbsp;against&nbsp;version&nbsp;2.5.0&nbsp;of&nbsp;the&nbsp;Protocol&nbsp;Buffer&nbsp;runtime&nbsp;library,&nbsp;which&nbsp;<span style="color: #0000FF; ">is</span>&nbsp;not&nbsp;compatible&nbsp;with&nbsp;the&nbsp;installed&nbsp;version&nbsp;(3.5.1).&nbsp;&nbsp;Contact&nbsp;the&nbsp;program&nbsp;author&nbsp;<span style="color: #0000FF; ">for</span>&nbsp;an&nbsp;update.&nbsp;&nbsp;If&nbsp;you&nbsp;compiled&nbsp;the&nbsp;program&nbsp;yourself,&nbsp;make&nbsp;sure&nbsp;that&nbsp;your&nbsp;headers&nbsp;are&nbsp;from&nbsp;the&nbsp;same&nbsp;version&nbsp;of&nbsp;Protocol&nbsp;Buffers&nbsp;<span style="color: #0000FF; ">as</span>&nbsp;your&nbsp;link-time&nbsp;library.&nbsp;&nbsp;(Version&nbsp;verification&nbsp;failed&nbsp;<span style="color: #0000FF; ">in</span>&nbsp;"google/protobuf/descriptor.pb.cc".)<br />
terminate&nbsp;called&nbsp;after&nbsp;throwing&nbsp;an&nbsp;instance&nbsp;of&nbsp;'google::protobuf::FatalException'<br />
&nbsp;&nbsp;what():&nbsp;&nbsp;This&nbsp;program&nbsp;was&nbsp;compiled&nbsp;against&nbsp;version&nbsp;2.5.0&nbsp;of&nbsp;the&nbsp;Protocol&nbsp;Buffer&nbsp;runtime&nbsp;library,&nbsp;which&nbsp;<span style="color: #0000FF; ">is</span>&nbsp;not&nbsp;compatible&nbsp;with&nbsp;the&nbsp;installed&nbsp;version&nbsp;(3.5.1).&nbsp;&nbsp;Contact&nbsp;the&nbsp;program&nbsp;author&nbsp;<span style="color: #0000FF; ">for</span>&nbsp;an&nbsp;update.&nbsp;&nbsp;If&nbsp;you&nbsp;compiled&nbsp;the&nbsp;program&nbsp;yourself,&nbsp;make&nbsp;sure&nbsp;that&nbsp;your&nbsp;headers&nbsp;are&nbsp;from&nbsp;the&nbsp;same&nbsp;version&nbsp;of&nbsp;Protocol&nbsp;Buffers&nbsp;<span style="color: #0000FF; ">as</span>&nbsp;your&nbsp;link-time&nbsp;library.&nbsp;&nbsp;(Version&nbsp;verification&nbsp;failed&nbsp;<span style="color: #0000FF; ">in</span>&nbsp;"google/protobuf/descriptor.pb.cc".)</div>
<br />
2、解决<br />
修改libShared.so编译脚本中， 编译依赖库protobuf时，添加命令： -fvisibility=hidden<br />
<div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
-->./configure&nbsp;--with-pic&nbsp;--disable-shared&nbsp;--enable-<span style="color: #0000FF; ">static</span>&nbsp;"CFLAGS=-fvisibility=hidden"<br />
</div>
<br />
3、命令：<br />
查询对外符号：<br />
<div>nm -CD libshared.so | grep " T" | grep google<br />
临时添加链接目录：<br />
<div>export LD_LIBRARY_PATH=XXX</div>
</div>
<span style="color: rgba(0, 0, 0, 0); font-family: &quot;PT Serif&quot;; font-size: 0px;"><br />
<br />
trol over symbol exports in GCCaaaa</span><br />
<span style="color: rgba(0, 0, 0, 0); font-family: &quot;PT Serif&quot;; font-size: 0px;">Control over symbol exports in GCC</span><img src ="http://www.cppblog.com/markqian86/aggbug/217481.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-10-14 17:18 <a href="http://www.cppblog.com/markqian86/archive/2020/10/14/217481.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>速度之王 — LZ4压缩算法</title><link>http://www.cppblog.com/markqian86/archive/2020/08/14/217421.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Fri, 14 Aug 2020 07:57:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/08/14/217421.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217421.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/08/14/217421.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217421.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217421.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: LZ4&nbsp;(Extremely Fast Compression algorithm)项目：http://code.google.com/p/lz4/作者：Yann Collet本文作者：zhangskd @ csdn blog&nbsp;简介&nbsp;LZ4 is a very fast lossless compression algorithm, providing compres...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2020/08/14/217421.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/217421.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-08-14 15:57 <a href="http://www.cppblog.com/markqian86/archive/2020/08/14/217421.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ssh PublicKey免密登录服务器</title><link>http://www.cppblog.com/markqian86/archive/2020/06/28/217387.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Sun, 28 Jun 2020 07:52:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/06/28/217387.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217387.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/06/28/217387.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217387.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217387.html</trackback:ping><description><![CDATA[<div><div><p>建立PublicKey登陆步骤其实非常简单，总结来说就是将客户端生成的的ssh public key添加到服务器的<code>~/.ssh/authorized_keys</code>文件中，即可实现ssh的免密码登录。</p> <blockquote> <h4>步骤</h4> </blockquote> <h5>1.客户端生成公钥和密钥</h5> <h5>2.将公钥配置到服务器即可</h5> <h4>1.客户端生成公钥和密钥</h4> <p>在客户端生成公钥密钥 附一篇<a href="https://link.jianshu.com?t=https://www.liaohuqiu.net/cn/posts/ssh-keygen-abc/" target="_blank" rel="nofollow">ssh-keygen 基本用法</a></p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-ruby"=""><code language-ruby"="">$ cd <span punctuation"="">.</span>ssh<span operator"="">/</span> $ ssh<span operator"="">-</span>keygen <span operator"="">-</span>t rsa  <span operator"="">-</span><span constant"="">C</span> <span string"="">"My-key"</span> </code></pre></div> <p>然后一路回车， 使用默认值即可</p> <p>使用<code>ls</code> 命令可以看到当前目录下的文件，有了个  <code>id_rsa</code>和<code>id_rsa.pub</code>，前者是密钥，后者是公钥。<br /> </p><div> <div style="max-width: 682px; max-height: 478px;"> <div style="padding-bottom: 70.09%;"></div> <div data-width="682" data-height="478"><img data-original-src="//upload-images.jianshu.io/upload_images/5473862-85d1d1414dd7d462.png" src="//upload-images.jianshu.io/upload_images/5473862-85d1d1414dd7d462.png" data-original-width="682" data-original-height="478" data-original-format="image/png" data-original-filesize="38333" data-image-index="0" style="cursor: zoom-in;"  alt="" /></div> </div> <div></div> </div><p>&nbsp;</p> <p>查看公钥</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-ruby"=""><code language-ruby"="">$ cat id_rsa<span punctuation"="">.</span>pub </code></pre></div> <p>复制公钥</p> <h4>2.将公钥配置到服务器</h4> <p>先尝试进入 <code>.ssh</code> 看看目录是否存在</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-bash"=""><code language-bash"="">$ cd .ssh </code></pre></div> <p>若不存在则新建一个</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-undefined"=""><code language-undefined"="">$ mkdir ~/.ssh </code></pre></div> <p>然后修改权限</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-ruby"=""><code language-ruby"="">$ chmod <span number"="">700</span> <span punctuation"="">.</span>ssh </code></pre></div> <p>接着再进入.ssh，然后修改将公钥添加到authorized_keys</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-ruby"=""><code language-ruby"="">$ vim authorized_keys </code></pre></div> <p>按<code>i</code>，然后将刚刚复制的密钥粘贴到这里，按<code>esc</code>，再按<code>:</code>，输入<code>wq</code>保存并退出<br /> 接着修改权限</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-ruby"=""><code language-ruby"="">$ chmod <span number"="">600</span> <span operator"="">~</span><span operator"="">/</span><span punctuation"="">.</span>ssh<span operator"="">/</span><span operator"="">*</span> </code></pre></div> <p>做好配置之后，通过ssh可以直接登录了。</p> <h4>简化登陆指令</h4> <p>我们可以利用ssh的配置文件来简化我们登陆的操作<br /> 配置文件在<code>~/.ssh/config</code><br /> 我们可以修改这个文件（如果不存在则新建一个）</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-undefined"=""><code language-undefined"="">Host            YourName HostName        YourServer User            root </code></pre></div> <p>YourName可以改成任意名字<br /> 如果修改了端口则需要加上<code>Port</code>参数<br /> 还有各种各样的参数可以查看帮助</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-ruby"=""><code language-ruby"="">$ man ssh_config </code></pre></div> <p>配置完后可以这样登陆服务器</p> <div><button type="button" aria-label="复制代码"><em aria-label="icon: copy"  anticon-copy"=""><svg viewbox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"></svg></em></button><pre language-ruby"=""><code language-ruby"="">$ ssh <span constant"="">YourName</span> </code></pre></div> <p>Enjoy it~</p></div><br /><br />作者：河里的肥鱼<br />链接：https://www.jianshu.com/p/1600fbf01917<br />来源：简书<br />著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</div><img src ="http://www.cppblog.com/markqian86/aggbug/217387.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-06-28 15:52 <a href="http://www.cppblog.com/markqian86/archive/2020/06/28/217387.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>执行shell脚本时提示bad interpreter:No such file or directory的解决办法</title><link>http://www.cppblog.com/markqian86/archive/2020/06/25/217380.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 25 Jun 2020 03:30:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/06/25/217380.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217380.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/06/25/217380.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217380.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217380.html</trackback:ping><description><![CDATA[<div>故障现象：在终端直接cd /var正常，在shell脚本中执行则报错。原因是脚本是在windows平台下写的，换行符与linux不同，造成脚本不能正确执行</div><div></div><div>出现bad interpreter:No such file or directory（没有那个文件或目录）的原因，是文件格式的问题。这个文件是在Windows下编写的。换行的方式与Unix不一样，但是在vim下面如果不Set一下又完全看不出来。</div><div></div><div></div><div>问题分析：</div><div>1、将windows 下编写好的SHELL文件，传到linux下执行，提示出错。</div><div>2、出错信息：bad interpreter: 没有那个文件或目录。</div><div></div><div>问题原因：</div><div>因为操作系统是windows，在windows下编辑的脚本，所以有可能有不可见字符。脚本文件是DOS格式的</div><div>即每一行的行尾以\r\n来标识, 其ASCII码分别是0x0D, 0x0A.</div><div></div><div>解决方法：</div><div>可以有很多种办法看这个文件是DOS格式的还是UNIX格式的, 还是MAC格式的</div><div>（1） vim filename</div><div>然后用命令 :set ff</div><div>可看到dos或unix的字样，如果的确是dos格式的, 那么用set ff=unix把它强制为unix格式的,，然后存盘退出后就可运行。</div><div></div><div>（set ff=unix : 告诉 vi 编辑器，使用unix换行符，个人使用以上方法解决，简单方便，推荐此方式）</div><div></div><div></div><div>###############分割线##############</div><div></div><div>转换不同平台的文本文件格式可以用</div><div>1. unix2dos或dos2unix这两个小程序来做. 很简单. 在djgpp中这两个程序的名字叫dtou和utod, u代表unix, d代表dos</div><div>2. 也可以用sed 这样的工具来做:</div><div></div><div>复制代码代码如下:</div><div>sed &#8216;s/^M//' filename &gt; tmp_filename</div><div>mv -f tmp_filename filename</div><div></div><div>特别说明：^M并不是按键shift + 6产生的^和字母M, 它是一个字符, 其ASCII是0x0D, 生成它的办法是先按CTRL+V, 然后再回车(或CTRL+M)</div><div></div><div>另外, 当SHELL程序报告command not found时, 总是去检查一下你的PATH里面有没有程序要用到的每一个命令(没指定绝对路径的那种). 你这么小的程序, 可以一行一行核对。</div><div>附：少写一个/引发的没有那个文件或目录问题</div><div></div><div>&nbsp; &nbsp;今天在翻看以前写的简单的shell脚本时,发现一个问题:</div><div></div><div>&nbsp; &nbsp;当./运行时总是提示:&nbsp; (bash: ./hello.sh: bin/bash: 坏的解释器: 没有那个文件或目录),但是当用sh运行时正确.</div><div></div><div>&nbsp; &nbsp;原来的脚本:</div><div></div><div>&nbsp; &nbsp;(试试看你能否一眼看出错误)</div><div></div><div>复制代码代码如下:</div><div></div><div>&nbsp; &nbsp;#!bin/bash</div><div>&nbsp; &nbsp;echo "Hello Linux!"</div><div></div><div></div><div>&nbsp;</div><div>&nbsp; &nbsp;后来几番检查发现自己写的丢了一些东西.</div><div></div><div>&nbsp; &nbsp;应该把第一行改成&nbsp; #!/bin/bash ，少写了一个/</div><div></div><div>&nbsp; &nbsp;唉,很简单的问题,自己以前没有发现还有这样的错误! shell脚本的确好用,可唯一难的就是格式要求太高!</div><div></div><div>参考博文： http://www.jb51.net/article/48784.htmhttp://www.jb51.net/article/48784.htm</div><div>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</div><div>版权声明：本文为CSDN博主「russ44」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。</div><div>原文链接：https://blog.csdn.net/russ44/article/details/51694047</div><img src ="http://www.cppblog.com/markqian86/aggbug/217380.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-06-25 11:30 <a href="http://www.cppblog.com/markqian86/archive/2020/06/25/217380.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用vim打开后中文乱码怎么办</title><link>http://www.cppblog.com/markqian86/archive/2020/06/23/217372.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Tue, 23 Jun 2020 09:10:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/06/23/217372.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217372.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/06/23/217372.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217372.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217372.html</trackback:ping><description><![CDATA[<div><div>1、设置~下的.vimrc文件，加上fileencodings、enc、fencs，代码如下：<br /><div><pre><code>set fileencodings=utf-8,gb2312,gb18030,gbk,ucs-bom,cp936,latin1 set enc=utf8 set fencs=utf8,gbk,gb2312,gb18030</code></pre></div></div><br /><br />作者：我反对这门亲事<br />链接：https://www.zhihu.com/question/22363620/answer/56603960<br />来源：知乎<br />著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</div><img src ="http://www.cppblog.com/markqian86/aggbug/217372.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-06-23 17:10 <a href="http://www.cppblog.com/markqian86/archive/2020/06/23/217372.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>CentOS 7下yum安装MariaDB</title><link>http://www.cppblog.com/markqian86/archive/2020/06/23/217371.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Tue, 23 Jun 2020 03:02:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/06/23/217371.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217371.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/06/23/217371.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217371.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217371.html</trackback:ping><description><![CDATA[<h1>引言</h1><p style="box-sizing: border-box; margin: 1em 0px; font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft Yahei&quot;, Arial; font-size: 16px; background-color: #ffffff;">之前测试mysqldump的导入，需要另外一台数据库，自己有一台CentOS 7系统的服务器。<br style="box-sizing: border-box;" />于是需要在CentOS 7安装mysql，之前并没有怎么使用CentOS 7。而且CentOS 7下mysql下替换成MariaDB了（MariaDB是从MySQL fork过来的，和MySQL有很好的兼容性 ）。所以这里记录下。</p><h1>CentOS 7下yum安装MariaDB</h1><p style="box-sizing: border-box; margin: 1em 0px; font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft Yahei&quot;, Arial; font-size: 16px; background-color: #ffffff;">由于作为测试服务器使用，所以使用yum快速安装。如下：</p><pre style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0px; font-family: monospace, serif; font-size: 16px; overflow-wrap: break-word; position: relative; padding: 1em; overflow: auto; background-color: #ffffff;">yum install mariadb mariadb-server systemctl start mariadb   #启动mariadb systemctl enable mariadb  #设置开机自启动 mysql_secure_installation #设置root密码等相关 mysql -uroot -p           #测试登录</pre><img src ="http://www.cppblog.com/markqian86/aggbug/217371.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-06-23 11:02 <a href="http://www.cppblog.com/markqian86/archive/2020/06/23/217371.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Centos中文语言乱码解决方法</title><link>http://www.cppblog.com/markqian86/archive/2020/06/23/217370.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Tue, 23 Jun 2020 02:55:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/06/23/217370.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217370.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/06/23/217370.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217370.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217370.html</trackback:ping><description><![CDATA[<div></div><div>1、 查看自己系统有没有安装中文语言包，可使用locale -a命令列出所有可用的语言环境：</div><div></div><div></div><div></div><div></div><div></div><div>看里面是否有下面四项：</div><div></div><div></div><div></div><div>如果有，则不用安装，如果没有，需要重新安装，使用yum install kde-l10n-Chinese</div><div></div><div></div><div></div><div></div><div></div><div>2、 修改i18n和locale.conf的配置文件</div><div></div><div></div><div></div><div>第一，vim/etc/sysconfig/i18n，在里面添加如下两行代码：</div><div></div><div>LANG="zh_CN.UTF-8"</div><div></div><div>LC_ALL="zh_CN.UTF-8"</div><div></div><div></div><div></div><div></div><div></div><div>然后执行一下，以使刚修改的文件生效：source/etc/sysconfig/i18n</div><div></div><div></div><div></div><div></div><div></div><div>第二，vim /etc/locale.conf</div><div></div><div>添加：LANG="zh_CN.UTF-8"</div><div></div><div></div><div></div><div></div><div></div><div></div><div>同样执行一下，使刚修改的文件生效source /etc/locale.conf</div><div></div><div></div><div></div><div></div><div></div><div>可以看到书写中文字体时可以书写，且不会显示乱码了：</div><div></div><div></div><div></div><div></div><div></div><div>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</div><div>版权声明：本文为CSDN博主「鹤影随行」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。</div><div>原文链接：https://blog.csdn.net/hpf247/article/details/79981803</div><div></div><img src ="http://www.cppblog.com/markqian86/aggbug/217370.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-06-23 10:55 <a href="http://www.cppblog.com/markqian86/archive/2020/06/23/217370.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NIO 与 零拷贝</title><link>http://www.cppblog.com/markqian86/archive/2020/05/14/217303.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 14 May 2020 03:27:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/05/14/217303.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217303.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/05/14/217303.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217303.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217303.html</trackback:ping><description><![CDATA[<h3>零拷贝介绍</h3><ol style="margin: 0px 0px 1em 40px; padding: 0px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 16px; background-color: #ffffff;"><li style="margin: 0px; padding: 0px; list-style-type: decimal;">零拷贝是网络编程的关键, 很多性能优化都需要零拷贝。</li><li style="margin: 0px; padding: 0px; list-style-type: decimal;">在 Java程序中, 常用的零拷贝方式有m(memory)map[内存映射] 和 sendFile。它们在OS中又是怎样的设计?</li><li style="margin: 0px; padding: 0px; list-style-type: decimal;">NIO中如何使用零拷贝?</li></ol><h3>NIO 与 传统IO对比</h3><ul style="margin: 0px 0px 1em 40px; padding: 0px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 16px; background-color: #ffffff;"><li style="margin: 0px; padding: 0px; list-style-type: disc;"><p style="padding: 0px; line-height: 1.72222; margin-top: 10px; margin-bottom: 10px;">传统IO流程示意图</p><p style="padding: 0px; line-height: 1.72222; margin-top: 10px; margin-bottom: 10px;"><img src="https://images.cnblogs.com/cnblogs_com/ronnieyuan/1607877/o_1912090242471575855435947.png" alt="1575855435947" style="margin: 0px; padding: 0px; border: none; height: auto; max-width: 800px;" /></p><ul style="margin: 0px 0px 1em 40px; padding: 0px;"><li style="margin: 0px; padding: 0px; list-style-type: circle;">user context: 用户态</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">kernel context: 内核态</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">User space: 用户空间</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">Kernel space: 内核空间</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">Syscall read: 系统调用读取</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">Syscall write: 系统调用写入</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">Hard drive: 硬件驱动</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">kernel buffer: 内核态缓冲区</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">user buffer: 用户态缓冲区</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">socket buffer: 套接字缓存</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">protocol engine: 协议引擎</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">DMA: Direct Memory Access: 直接内存拷贝(不使用CPU)</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">总结: 4次拷贝, 3次状态切换, 效率不高</li></ul></li><li style="margin: 0px; padding: 0px; list-style-type: disc;"><p style="padding: 0px; line-height: 1.72222; margin-top: 10px; margin-bottom: 10px;">mmap优化流程示意图</p><p style="padding: 0px; line-height: 1.72222; margin-top: 10px; margin-bottom: 10px;"><img src="https://images.cnblogs.com/cnblogs_com/ronnieyuan/1607877/o_1912090242591575855870723.png" alt="1575855870723" style="margin: 0px; padding: 0px; border: none; height: auto; max-width: 800px;" /></p><ul style="margin: 0px 0px 1em 40px; padding: 0px;"><li style="margin: 0px; padding: 0px; list-style-type: circle;">mmap 通过内存映射, 将文件映射到内核缓冲区, 同时, 用户空间可以共享内核空间的数据。</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">这样, 在进行网络传输时, 就可以减少内核空间到用户空间的拷贝次数。</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">总结: 3次拷贝, 3次状态切换, 不是真正意义上的零拷贝。</li></ul></li><li style="margin: 0px; padding: 0px; list-style-type: disc;"><p style="padding: 0px; line-height: 1.72222; margin-top: 10px; margin-bottom: 10px;">sendFile Linux2.1版本优化流程示意图</p><p style="padding: 0px; line-height: 1.72222; margin-top: 10px; margin-bottom: 10px;"><img src="https://images.cnblogs.com/cnblogs_com/ronnieyuan/1607877/o_1912090243091575856449573.png" alt="1575856449573" style="margin: 0px; padding: 0px; border: none; height: auto; max-width: 800px;" /></p><ul style="margin: 0px 0px 1em 40px; padding: 0px;"><li style="margin: 0px; padding: 0px; list-style-type: circle;">数据根本不经过用户态, 直接从内核缓冲区进入到Socket Buffer, 同时, 由于和用户台完全无关, 就减少了一次上下文切换。</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">但是仍然有一次CPU拷贝, 不是真正的零拷贝(没有CPU拷贝)。</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">总结: 3次拷贝, 2次切换</li></ul></li><li style="margin: 0px; padding: 0px; list-style-type: disc;"><p style="padding: 0px; line-height: 1.72222; margin-top: 10px; margin-bottom: 10px;">sendFile Linux</p><p style="padding: 0px; line-height: 1.72222; margin-top: 10px; margin-bottom: 10px;"><img src="https://images.cnblogs.com/cnblogs_com/ronnieyuan/1607877/o_1912090243191575856952257.png" alt="1575856952257" style="margin: 0px; padding: 0px; border: none; height: auto; max-width: 800px;" /></p><ul style="margin: 0px 0px 1em 40px; padding: 0px;"><li style="margin: 0px; padding: 0px; list-style-type: circle;">避免了从内核缓冲区拷贝到Socket buffer的操作, 直接拷贝到协议栈, 从而再一次减少了数据拷贝。</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">其实是有一次cpu拷贝的, kernel buffer -&gt; socket buffer, 但是拷贝的信息很少, length, offset, 消耗低, 基本可以忽略。</li><li style="margin: 0px; padding: 0px; list-style-type: circle;">总结: 2次拷贝(如果忽略消耗低的cpu拷贝的话), 2次切换, 基本可以认为是零拷贝了。</li></ul></li></ul><h3>零拷贝理解</h3><ul style="margin: 0px 0px 1em 40px; padding: 0px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 16px; background-color: #ffffff;"><li style="margin: 0px; padding: 0px; list-style-type: disc;">零拷贝是从操作系统的角度来看的。内核缓冲区之间, 没有数据是重复的(只有kernel buffer有一份数据)。</li><li style="margin: 0px; padding: 0px; list-style-type: disc;">零拷贝不仅仅带来更少的数据复制, 还能带来其他的性能优势: 如更少的上下文切换, 更少的 CPU 缓存伪共享以及无CPU校验和计算。</li></ul><h3>mmap 与 sendFile 总结</h3><ul style="margin: 0px 0px 1em 40px; padding: 0px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 16px; background-color: #ffffff;"><li style="margin: 0px; padding: 0px; list-style-type: disc;">mmap适合小数据两读写, sendFile适合大文件传输</li><li style="margin: 0px; padding: 0px; list-style-type: disc;">mmap 需要3次上下文切换, 3次数据拷贝; sendFile 需要3次上下文切换, 最少2次数据拷贝。</li><li style="margin: 0px; padding: 0px; list-style-type: disc;">sendFile 可以利用 DMA 方式, 减少 CPU 拷贝, 而 mmap则不能(必须从内核拷贝到Socket缓冲区)。</li></ul><img src ="http://www.cppblog.com/markqian86/aggbug/217303.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-05-14 11:27 <a href="http://www.cppblog.com/markqian86/archive/2020/05/14/217303.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何避免TCP的TIME_WAIT状态</title><link>http://www.cppblog.com/markqian86/archive/2020/05/09/217294.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Sat, 09 May 2020 09:06:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/05/09/217294.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217294.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/05/09/217294.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217294.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217294.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 关于TCP连接的TIME-WAIT状态，它是为何而生，存在的意义是什么？&nbsp;&nbsp;&nbsp;&nbsp;让我们回忆一下，什么是TCP TIME-WAIT状态？如下图&nbsp;&nbsp;&nbsp;&nbsp;当TCP连接关闭之前，首先发起关闭的一方会进入TIME_WAIT状态（也就是主动关闭连接的一方才会产生TIME_WAIT），另一方可以快速回收连接。可以用ss -tan来查...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2020/05/09/217294.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/217294.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-05-09 17:06 <a href="http://www.cppblog.com/markqian86/archive/2020/05/09/217294.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>cmake 返回上层目录</title><link>http://www.cppblog.com/markqian86/archive/2020/05/06/217281.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Wed, 06 May 2020 09:09:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/05/06/217281.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217281.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/05/06/217281.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217281.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217281.html</trackback:ping><description><![CDATA[<div>string(REGEX REPLACE "(.*)/(.*)/(.*)" "\\1" PROJECT_INIT_PATH&nbsp; ${PROJECT_SOURCE_DIR})<br />message("上层目录=" $(PROJECT_INIT_PATH))<br /><br />举例：<br />PROJECT_SOURCE_DIR=/home/1/2/3/4<br />用正则表达式对目录进行匹配，最后一次匹配结果：<br /><span style="color: red;">(</span>/home/1/2<span style="color: red;">)</span>/<span style="color: red;">(</span>3<span style="color: red;">)</span>/<span style="color: red;">(</span>4<span style="color: red;">)</span>&nbsp;&nbsp;<br />string(REGEX REPLACE "(.*)/(.*)/(.*)" "\\<span style="color: red;">1</span>" PROJECT_INIT_PATH&nbsp; ${PROJECT_SOURCE_DIR}) 结果：/home/1/2</div>string(REGEX REPLACE "(.*)/(.*)/(.*)" "\\<span style="color: red;">2</span>" PROJECT_INIT_PATH&nbsp; ${PROJECT_SOURCE_DIR}) 结果：3<br />string(REGEX REPLACE "(.*)/(.*)/(.*)" "\\<span style="color: red;">3</span>" PROJECT_INIT_PATH&nbsp; ${PROJECT_SOURCE_DIR}) 结果：4<br /><span punctuation"="" style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #999999; font-family: &quot;Source Code Pro&quot;, &quot;DejaVu Sans Mono&quot;, &quot;Ubuntu Mono&quot;, &quot;Anonymous Pro&quot;, &quot;Droid Sans Mono&quot;, Menlo, Monaco, Consolas, Inconsolata, Courier, monospace, &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, sans-serif; white-space: pre; font-variant-numeric: normal !important; font-variant-east-asian: normal !important; font-stretch: normal !important; line-height: normal !important;"></span><img src ="http://www.cppblog.com/markqian86/aggbug/217281.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-05-06 17:09 <a href="http://www.cppblog.com/markqian86/archive/2020/05/06/217281.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>解决TIME_WAIT过多造成的问题</title><link>http://www.cppblog.com/markqian86/archive/2020/04/24/217260.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Fri, 24 Apr 2020 08:18:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/24/217260.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217260.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/24/217260.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217260.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217260.html</trackback:ping><description><![CDATA[<p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;"><strong style="margin: 0px; padding: 0px;"><img src="https://images2018.cnblogs.com/blog/1209537/201804/1209537-20180410142221304-1279996255.png" alt="" style="margin: 0px; padding: 0px; border: 0px; height: auto; max-width: 820px;" /></strong></p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;"><strong style="margin: 0px; padding: 0px;">1、 time_wait的作用：</strong></p><div style="margin: 5px 0px; font-size: 12px !important;"><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; word-wrap: break-word; white-space: pre-wrap; font-family: &quot;Courier New&quot; !important;"><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">TIME_WAIT状态存在的理由： </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">1</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">）<strong style="margin: 0px; padding: 0px;">可靠地实现TCP全双工连接的终止</strong>    在进行关闭连接四次挥手协议时，最后的ACK是由主动关闭端发出的，如果这个最终的ACK丢失，服务器将重发最终的FIN， 因此客户端必须维护状态信息允许它重发最终的ACK。如果不维持这个状态信息，那么客户端将响应RST分节，服务器将此分节解释成一个错误（在java中会抛出connection reset的SocketException)。 因而，要实现TCP全双工连接的正常终止，必须处理终止序列四个分节中任何一个分节的丢失情况，主动关闭的客户端必须维持状态信息进入TIME_WAIT状态。   </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">2</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">）<strong style="margin: 0px; padding: 0px;">允许老的重复分节在网络中消逝</strong>  TCP分节可能由于路由器异常而&#8220;迷途&#8221;，在迷途期间，TCP发送端可能因确认超时而重发这个分节，迷途的分节在路由器修复后也会被送到最终目的地，这个原来的迷途分节就称为lost duplicate。 在关闭一个TCP连接后，马上又重新建立起一个相同的IP地址和端口之间的TCP连接，后一个连接被称为前一个连接的化身（incarnation)，那么有可能出现这种情况，前一个连接的迷途重复分组在前一个连接终止后出现，从而被误解成从属于新的化身。 为了避免这个情况，TCP不允许处于TIME_WAIT状态的连接启动一个新的化身，因为TIME_WAIT状态持续2MSL，就可以保证当成功建立一个TCP连接的时候，来自连接先前化身的重复分组已经在网络中消逝。</span></pre><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div></div><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;"><strong style="margin: 0px; padding: 0px;">2、大量TIME_WAIT造成的影响：</strong></p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;&nbsp;在<strong style="margin: 0px; padding: 0px;">高并发短连接</strong>的TCP服务器上，当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。如果客户端的并发量持续很高，此时部分客户端就会显示连接不上。<br style="margin: 0px; padding: 0px;" />我来解释下这个场景。主动正常关闭TCP连接，都会出现TIMEWAIT。</p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">为什么我们要关注这个高并发短连接呢？有两个方面需要注意：<br style="margin: 0px; padding: 0px;" />1.&nbsp;<strong style="margin: 0px; padding: 0px;">高并发可以让服务器在短时间范围内同时占用大量端口</strong>，而端口有个0~65535的范围，并不是很多，刨除系统和其他服务要用的，剩下的就更少了。<br style="margin: 0px; padding: 0px;" />2. 在这个场景中，<strong style="margin: 0px; padding: 0px;">短连接表示&#8220;业务处理+传输数据的时间 远远小于 TIMEWAIT超时的时间&#8221;的连接</strong>。</p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; 这里有个相对长短的概念，比如取一个web页面，1秒钟的http短连接处理完业务，在关闭连接之后，这个业务用过的端口会停留在TIMEWAIT状态几分钟，而这几分钟，其他HTTP请求来临的时候是无法占用此端口的(占着茅坑不拉翔)。单用这个业务计算服务器的利用率会发现，服务器干正经事的时间和端口（资源）被挂着无法被使用的时间的比例是 1：几百，服务器资源严重浪费。（说个题外话，从这个意义出发来考虑服务器性能调优的话，长连接业务的服务就不需要考虑TIMEWAIT状态。同时，假如你对服务器业务场景非常熟悉，你会发现，在实际业务场景中，一般<strong style="margin: 0px; padding: 0px;">长连接对应的业务的并发量并不会很高</strong>。<br style="margin: 0px; padding: 0px;" />&nbsp; &nbsp; &nbsp;综合这两个方面，持续的到达一定量的高并发短连接，会使服务器因端口资源不足而拒绝为一部分客户服务。同时，这些端口都是服务器临时分配，无法用SO_REUSEADDR选项解决这个问题。</p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;"><strong style="margin: 0px; padding: 0px;">关于time_wait的反思</strong>：</p><div style="margin: 5px 0px; font-size: 12px !important;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; word-wrap: break-word; white-space: pre-wrap; font-family: &quot;Courier New&quot; !important;"><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">存在即是合理的，既然TCP协议能盛行四十多年，就证明他的设计合理性。所以我们尽可能的使用其原本功能。 依靠TIME_WAIT状态来保证我的服务器程序健壮，服务功能正常。 那是不是就不要性能了呢？并不是。如果服务器上跑的短连接业务量到了我真的必须处理这个TIMEWAIT状态过多的问题的时候，我的原则是尽量处理，而不是跟TIMEWAIT干上，非先除之而后快。 如果尽量处理了，还是解决不了问题，仍然拒绝服务部分请求，那我会采取负载均衡来抗这些高并发的短请求。持续十万并发的短连接请求，两台机器，每台5万个，应该够用了吧。一般的业务量以及国内大部分网站其实并不需要关注这个问题，一句话，达不到时才需要关注这个问题的访问量。</span></pre></div><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">小知识点：</p><div style="margin: 5px 0px; font-size: 12px !important;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; word-wrap: break-word; white-space: pre-wrap; font-family: &quot;Courier New&quot; !important;">TCP协议发表：1974年12月，卡恩、瑟夫的第一份TCP协议详细说明正式发表。当时美国国防部与三个科学家小组签定了完成TCP/IP的协议，结果由瑟夫领衔的小组捷足先登，首先制定出了通过详细定义的TCP/IP协议标准。当时作了一个试验，将信息包通过点对点的卫星网络，再通过陆地电缆<br style="margin: 0px; padding: 0px;" />，再通过卫星网络，再由地面传输，贯串欧洲和美国，经过各种电脑系统，全程9.4万公里竟然没有丢失一个数据位，远距离的可靠数据传输证明了TCP/IP协议的成功。</pre></div><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">&nbsp;</p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;"><strong style="margin: 0px; padding: 0px;">3、案列分析：</strong></p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">&nbsp; &nbsp; 首先，根据一个查询TCP连接数，来说明这个问题。</p><div style="margin: 5px 0px; font-size: 12px !important;"><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; word-wrap: break-word; white-space: pre-wrap; font-family: &quot;Courier New&quot; !important;"><span style="margin: 0px; padding: 0px; font-size: 14px; line-height: 1.5 !important;"><strong style="margin: 0px; padding: 0px;">netstat -ant|awk <span style="margin: 0px; padding: 0px; color: #800000; font-size: 12px !important; line-height: 1.5 !important;">'</span><span style="margin: 0px; padding: 0px; color: #800000; font-size: 12px !important; line-height: 1.5 !important;">/^tcp/ {++S[$NF]} END {for(a in S) print (a,S[a])}</span><span style="margin: 0px; padding: 0px; color: #800000; font-size: 12px !important; line-height: 1.5 !important;">'</span></strong></span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> LAST_ACK </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">14</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> SYN_RECV </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">348</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> ESTABLISHED </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">70</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> FIN_WAIT1 </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">229</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> FIN_WAIT2 </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">30</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> CLOSING </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">33</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> TIME_WAIT </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">18122</span></pre><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div></div><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;"><strong style="margin: 0px; padding: 0px;">状态描述：</strong></p><div style="margin: 5px 0px; font-size: 12px !important;"><img id="code_img_opened_44d05ae9-50c9-473b-a4e7-38580174b4cd" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="" style="margin: 0px; padding-top: 0px; padding-bottom: 0px; padding-left: 0px; border: 0px; height: auto; max-width: 820px;" /><div id="cnblogs_code_open_44d05ae9-50c9-473b-a4e7-38580174b4cd" style="margin: 0px;"><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; word-wrap: break-word; white-space: pre-wrap; font-family: &quot;Courier New&quot; !important;"><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">CLOSED：无连接是活动的或正在进行 LISTEN：服务器在等待进入呼叫 SYN_RECV：一个连接请求已经到达，等待确认 SYN_SENT：应用已经开始，打开一个连接 ESTABLISHED：正常数据传输状态 FIN_WAIT1：应用说它已经完成 FIN_WAIT2：另一边已同意释放 ITMED_WAIT：等待所有分组死掉 CLOSING：两边同时尝试关闭 TIME_WAIT：另一边已初始化一个释放 LAST_ACK：等待所有分组死掉</span></pre><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div></div></div><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;"><strong style="margin: 0px; padding: 0px;">命令解释：</strong></p><div style="margin: 5px 0px; font-size: 12px !important;"><img id="code_img_opened_5868f907-7a07-46c7-ab8f-cd4e19e665ee" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="" style="margin: 0px; padding-top: 0px; padding-bottom: 0px; padding-left: 0px; border: 0px; height: auto; max-width: 820px;" /><div id="cnblogs_code_open_5868f907-7a07-46c7-ab8f-cd4e19e665ee" style="margin: 0px;"><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; word-wrap: break-word; white-space: pre-wrap; font-family: &quot;Courier New&quot; !important;"><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">先来看看netstat： netstat </span>-<span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">n Active Internet connections (w</span>/<span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">o servers) Proto Recv</span>-Q Send-<span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">Q Local Address Foreign Address State tcp </span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">0</span> <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">0</span> <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">123.123</span>.<span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">123.123</span>:<span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">80</span> <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">234.234</span>.<span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">234.234</span>:<span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">12345</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> TIME_WAIT 你实际执行这条命令的时候，可能会得到成千上万条类似上面的记录，不过我们就拿其中的一条就足够了。  再来看看awk： </span>/^tcp/<span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> 滤出tcp开头的记录，屏蔽udp, socket等无关记录。 state[]相当于定义了一个名叫state的数组 NF 表示记录的字段数，如上所示的记录，NF等于6 $NF 表示某个字段的值，如上所示的记录，$NF也就是$</span><span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">6</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">，表示第6个字段的值，也就是TIME_WAIT state[$NF]表示数组元素的值，如上所示的记录，就是state[TIME_WAIT]状态的连接数 </span>++<span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">state[$NF]表示把某个数加一，如上所示的记录，就是把state[TIME_WAIT]状态的连接数加一 END 表示在最后阶段要执行的命令 </span><span style="margin: 0px; padding: 0px; color: #0000ff; line-height: 1.5 !important;">for</span>(key <span style="margin: 0px; padding: 0px; color: #0000ff; line-height: 1.5 !important;">in</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> state) 遍历数组</span></pre><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div></div></div><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">&nbsp;</p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;"><strong style="margin: 0px; padding: 0px;">如何<span style="margin: 0px; padding: 0px; background-color: #ff0000;">尽量</span>处理TIMEWAIT过多?</strong></p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">编辑内核文件/etc/sysctl.conf，加入以下内容：</p><div style="margin: 5px 0px; font-size: 12px !important;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; word-wrap: break-word; white-space: pre-wrap; font-family: &quot;Courier New&quot; !important;">net.ipv4.tcp_syncookies = <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">1</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;"> 表示开启SYN Cookies。当出现SYN等待队列溢出时，启用cookies来处理，可防范少量SYN攻击，默认为0，表示关闭； net.ipv4.tcp_tw_reuse </span>= <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">1</span> 表示开启重用。允许将TIME-<span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">WAIT sockets重新用于新的TCP连接，默认为0，表示关闭； net.ipv4.tcp_tw_recycle </span>= <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">1</span> 表示开启TCP连接中TIME-<span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">WAIT sockets的快速回收，默认为0，表示关闭。 net.ipv4.tcp_fin_timeout 修改系默认的 TIMEOUT 时间</span></pre></div><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">然后执行 /sbin/sysctl -p 让参数生效.</p><div style="margin: 5px 0px; font-size: 12px !important;"><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; word-wrap: break-word; white-space: pre-wrap; font-family: &quot;Courier New&quot; !important;">/etc/sysctl.conf是一个允许改变正在运行中的Linux系统的接口，它包含一些TCP/IP堆栈和虚拟内存系统的高级选项，修改内核参数永久生效。</pre></div><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">简单来说，就是打开系统的TIMEWAIT重用和快速回收。</p><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">如果以上配置调优后性能还不理想，可继续修改一下配置：</p><div style="margin: 5px 0px; font-size: 12px !important;"><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div><pre style="margin-top: 0px; margin-bottom: 0px; padding: 0px; word-wrap: break-word; white-space: pre-wrap; font-family: &quot;Courier New&quot; !important;">vi /etc/<span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">sysctl.conf net.ipv4.tcp_keepalive_time </span>= <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">1200</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">  #表示当keepalive起用的时候，TCP发送keepalive消息的频度。缺省是2小时，改为20分钟。 net.ipv4.ip_local_port_range </span>= <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">1024</span> <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">65000</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">  #表示用于向外连接的端口范围。缺省情况下很小：32768到61000，改为1024到65000。 net.ipv4.tcp_max_syn_backlog </span>= <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">8192</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">  #表示SYN队列的长度，默认为1024，加大队列长度为8192，可以容纳更多等待连接的网络连接数。 net.ipv4.tcp_max_tw_buckets </span>= <span style="margin: 0px; padding: 0px; color: #800080; line-height: 1.5 !important;">5000</span><span style="margin: 0px; padding: 0px; line-height: 1.5 !important;">  #表示系统同时保持TIME_WAIT套接字的最大数量，如果超过这个数字，TIME_WAIT套接字将立刻被清除并打印警告信息。 默认为180000，改为5000。对于Apache、Nginx等服务器，上几行的参数可以很好地减少TIME_WAIT套接字数量，但是对于 Squid，效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量，避免Squid服务器被大量的TIME_WAIT套接字拖死。</span></pre><div style="margin: 5px 0px 0px;"><span style="margin: 0px; padding: 0px 5px 0px 0px; line-height: 1.5 !important;"><a title="复制代码" style="margin: 0px; padding: 0px; text-decoration-line: underline; border: none !important;"><img src="https://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="margin: 0px; padding: 0px; height: auto; max-width: 820px; border: none !important;" /></a></span></div></div><p style="padding: 0px; margin-top: 10px; margin-bottom: 10px; font-family: 微软雅黑, PTSans, Arial, sans-serif; font-size: 15px; background-color: #ffffff;">&nbsp;</p><img src ="http://www.cppblog.com/markqian86/aggbug/217260.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-24 16:18 <a href="http://www.cppblog.com/markqian86/archive/2020/04/24/217260.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>LINUX内核内存屏障</title><link>http://www.cppblog.com/markqian86/archive/2020/04/24/217258.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Fri, 24 Apr 2020 02:51:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/24/217258.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217258.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/24/217258.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217258.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217258.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;=================&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LINUX内核内存屏障&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2020/04/24/217258.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/217258.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-24 10:51 <a href="http://www.cppblog.com/markqian86/archive/2020/04/24/217258.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>无锁队列的实现</title><link>http://www.cppblog.com/markqian86/archive/2020/04/23/217257.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 23 Apr 2020 09:49:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/23/217257.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217257.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/23/217257.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217257.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217257.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 关于无锁队列的实现，网上有很多文章，虽然本文可能和那些文章有所重复，但是我还是想以我自己的方式把这些文章中的重要的知识点串起来和大家讲一讲这个技术。下面开始正文。目录关于CAS等原子操作无锁队列的链表实现CAS的ABA问题解决ABA的问题用数组实现无锁队列&nbsp;小结关于CAS等原子操作在开始说无锁队列之前，我们需要知道一个很重要的技术就是CAS操作&#8212;&#8212;Compare ...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2020/04/23/217257.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/217257.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-23 17:49 <a href="http://www.cppblog.com/markqian86/archive/2020/04/23/217257.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>巧夺天工的kfifo</title><link>http://www.cppblog.com/markqian86/archive/2020/04/23/217256.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 23 Apr 2020 08:38:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/23/217256.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217256.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/23/217256.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217256.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217256.html</trackback:ping><description><![CDATA[<div>Linux kernel里面从来就不缺少简洁，优雅和高效的代码，只是我们缺少发现和品味的眼光。在Linux kernel里面，简洁并不表示代码使用神出鬼没的超然技巧，相反，它使用的不过是大家非常熟悉的基础数据结构，但是kernel开发者能从基础的数据结构中，提炼出优美的特性。&nbsp;</div><div>kfifo就是这样的一类优美代码，它十分简洁，绝无多余的一行代码，却非常高效。&nbsp;</div><div>关于kfifo信息如下：</div><div></div><div>本文分析的原代码版本： 2.6.24.4</div><div></div><div>kfifo的定义文件： kernel/kfifo.c</div><div></div><div>kfifo的头文件： include/linux/kfifo.h</div><div></div><div>kfifo概述</div><div>kfifo是内核里面的一个First In First Out数据结构，它采用环形循环队列的数据结构来实现；它提供一个无边界的字节流服务，最重要的一点是，它使用并行无锁编程技术，即当它用于只有一个入队线程和一个出队线程的场情时，两个线程可以并发操作，而不需要任何加锁行为，就可以保证kfifo的线程安全。&nbsp;</div><div>kfifo代码既然肩负着这么多特性，那我们先一敝它的代码：</div><div></div><div>struct kfifo {</div><div>&nbsp; &nbsp; unsigned char *buffer;&nbsp; &nbsp; /* the buffer holding the data */</div><div>&nbsp; &nbsp; unsigned int size;&nbsp; &nbsp; /* the size of the allocated buffer */</div><div>&nbsp; &nbsp; unsigned int in;&nbsp; &nbsp; /* data is added at offset (in % size) */</div><div>&nbsp; &nbsp; unsigned int out;&nbsp; &nbsp; /* data is extracted from off. (out % size) */</div><div>&nbsp; &nbsp; spinlock_t *lock;&nbsp; &nbsp; /* protects concurrent modifications */</div><div>};</div><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>这是kfifo的数据结构，kfifo主要提供了两个操作，__kfifo_put(入队操作)和__kfifo_get(出队操作)。 它的各个数据成员如下：</div><div></div><div>buffer: 用于存放数据的缓存</div><div></div><div>size: buffer空间的大小，在初化时，将它向上扩展成2的幂</div><div></div><div>lock: 如果使用不能保证任何时间最多只有一个读线程和写线程，需要使用该lock实施同步。</div><div></div><div>in, out: 和buffer一起构成一个循环队列。 in指向buffer中队头，而且out指向buffer中的队尾，它的结构如示图如下：</div><div></div><div>+--------------------------------------------------------------+</div><div>|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&lt;----------data----------&gt;|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |</div><div>+--------------------------------------------------------------+</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ^&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ^</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; in&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;size</div><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>当然，内核开发者使用了一种更好的技术处理了in, out和buffer的关系，我们将在下面进行详细分析。</div><div></div><div>kfifo功能描述</div><div>kfifo提供如下对外功能规格</div><div></div><div>只支持一个读者和一个读者并发操作</div><div>无阻塞的读写操作，如果空间不够，则返回实际访问空间</div><div>kfifo_alloc 分配kfifo内存和初始化工作</div><div>struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask, spinlock_t *lock)</div><div>{</div><div>&nbsp; &nbsp; unsigned char *buffer;</div><div>&nbsp; &nbsp; struct kfifo *ret;</div><div></div><div>&nbsp; &nbsp; /*</div><div>&nbsp; &nbsp; &nbsp;* round up to the next power of 2, since our 'let the indices</div><div>&nbsp; &nbsp; &nbsp;* wrap' tachnique works only in this case.</div><div>&nbsp; &nbsp; &nbsp;*/</div><div>&nbsp; &nbsp; if (size &amp; (size - 1)) {</div><div>&nbsp; &nbsp; &nbsp; &nbsp; BUG_ON(size &gt; 0x80000000);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; size = roundup_pow_of_two(size);</div><div>&nbsp; &nbsp; }</div><div></div><div>&nbsp; &nbsp; buffer = kmalloc(size, gfp_mask);</div><div>&nbsp; &nbsp; if (!buffer)</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return ERR_PTR(-ENOMEM);</div><div></div><div>&nbsp; &nbsp; ret = kfifo_init(buffer, size, gfp_mask, lock);</div><div></div><div>&nbsp; &nbsp; if (IS_ERR(ret))</div><div>&nbsp; &nbsp; &nbsp; &nbsp; kfree(buffer);</div><div></div><div>&nbsp; &nbsp; return ret;</div><div>}</div><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div><div>9</div><div>10</div><div>11</div><div>12</div><div>13</div><div>14</div><div>15</div><div>16</div><div>17</div><div>18</div><div>19</div><div>20</div><div>21</div><div>22</div><div>23</div><div>24</div><div>25</div><div>这里值得一提的是，kfifo-&gt;size的值总是在调用者传进来的size参数的基础上向2的幂扩展，这是内核一贯的做法。这样的好处不言而喻&#8212;&#8212;对kfifo-&gt;size取模运算可以转化为与运算，如下：</div><div></div><div>kfifo-&gt;in % kfifo-&gt;size 可以转化为 kfifo-&gt;in &amp; (kfifo-&gt;size &#8211; 1)</div><div></div><div>在kfifo_alloc函数中，使用size &amp; (size &#8211; 1)来判断size 是否为2幂，如果条件为真，则表示size不是2的幂，然后调用roundup_pow_of_two将之向上扩展为2的幂。</div><div></div><div>这都是常用的技巧，只不过大家没有将它们结合起来使用而已，下面要分析的__kfifo_put和__kfifo_get则是将kfifo-&gt;size的特点发挥到了极致。</div><div></div><div>__kfifo_put和__kfifo_get巧妙的入队和出队</div><div>__kfifo_put是入队操作，它先将数据放入buffer里面，最后才修改in参数；__kfifo_get是出队操作，它先将数据从buffer中移走，最后才修改out。你会发现in和out两者各司其职。</div><div></div><div>下面是__kfifo_put和__kfifo_get的代码</div><div></div><div>unsigned int __kfifo_put(struct kfifo *fifo,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;unsigned char *buffer, unsigned int len)</div><div>{</div><div>&nbsp; &nbsp; unsigned int l;</div><div></div><div>&nbsp; &nbsp; len = min(len, fifo-&gt;size - fifo-&gt;in + fifo-&gt;out);</div><div></div><div>&nbsp; &nbsp; /*</div><div>&nbsp; &nbsp; &nbsp;* Ensure that we sample the fifo-&gt;out index -before- we</div><div>&nbsp; &nbsp; &nbsp;* start putting bytes into the kfifo.</div><div>&nbsp; &nbsp; &nbsp;*/</div><div></div><div>&nbsp; &nbsp; smp_mb();</div><div></div><div>&nbsp; &nbsp; /* first put the data starting from fifo-&gt;in to buffer end */</div><div>&nbsp; &nbsp; l = min(len, fifo-&gt;size - (fifo-&gt;in &amp; (fifo-&gt;size - 1)));</div><div>&nbsp; &nbsp; memcpy(fifo-&gt;buffer + (fifo-&gt;in &amp; (fifo-&gt;size - 1)), buffer, l);</div><div></div><div>&nbsp; &nbsp; /* then put the rest (if any) at the beginning of the buffer */</div><div>&nbsp; &nbsp; memcpy(fifo-&gt;buffer, buffer + l, len - l);</div><div></div><div>&nbsp; &nbsp; /*</div><div>&nbsp; &nbsp; &nbsp;* Ensure that we add the bytes to the kfifo -before-</div><div>&nbsp; &nbsp; &nbsp;* we update the fifo-&gt;in index.</div><div>&nbsp; &nbsp; &nbsp;*/</div><div></div><div>&nbsp; &nbsp; smp_wmb();</div><div></div><div>&nbsp; &nbsp; fifo-&gt;in += len;</div><div></div><div>&nbsp; &nbsp; return len;</div><div>}</div><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div><div>9</div><div>10</div><div>11</div><div>12</div><div>13</div><div>14</div><div>15</div><div>16</div><div>17</div><div>18</div><div>19</div><div>20</div><div>21</div><div>22</div><div>23</div><div>24</div><div>25</div><div>26</div><div>27</div><div>28</div><div>29</div><div>30</div><div>31</div><div>32</div><div>33</div><div>奇怪吗？代码完全是线性结构，没有任何if-else分支来判断是否有足够的空间存放数据。内核在这里的代码非常简洁，没有一行多余的代码。</div><div></div><div>l = min(len, fifo-&gt;size - (fifo-&gt;in &amp; (fifo-&gt;size - 1)));</div><div></div><div>这个表达式计算当前写入的空间，换成人可理解的语言就是：</div><div></div><div>l = kfifo可写空间和预期写入空间的最小值</div><div></div><div>使用min宏来代if-else分支</div><div></div><div>__kfifo_get也应用了同样技巧，代码如下：</div><div></div><div>unsigned int __kfifo_get(struct kfifo *fifo,</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;unsigned char *buffer, unsigned int len)</div><div>{</div><div>&nbsp; &nbsp; unsigned int l;</div><div></div><div>&nbsp; &nbsp; len = min(len, fifo-&gt;in - fifo-&gt;out);</div><div></div><div>&nbsp; &nbsp; /*</div><div>&nbsp; &nbsp; &nbsp;* Ensure that we sample the fifo-&gt;in index -before- we</div><div>&nbsp; &nbsp; &nbsp;* start removing bytes from the kfifo.</div><div>&nbsp; &nbsp; &nbsp;*/</div><div></div><div>&nbsp; &nbsp; smp_rmb();</div><div></div><div>&nbsp; &nbsp; /* first get the data from fifo-&gt;out until the end of the buffer */</div><div>&nbsp; &nbsp; l = min(len, fifo-&gt;size - (fifo-&gt;out &amp; (fifo-&gt;size - 1)));</div><div>&nbsp; &nbsp; memcpy(buffer, fifo-&gt;buffer + (fifo-&gt;out &amp; (fifo-&gt;size - 1)), l);</div><div></div><div>&nbsp; &nbsp; /* then get the rest (if any) from the beginning of the buffer */</div><div>&nbsp; &nbsp; memcpy(buffer + l, fifo-&gt;buffer, len - l);</div><div></div><div>&nbsp; &nbsp; /*</div><div>&nbsp; &nbsp; &nbsp;* Ensure that we remove the bytes from the kfifo -before-</div><div>&nbsp; &nbsp; &nbsp;* we update the fifo-&gt;out index.</div><div>&nbsp; &nbsp; &nbsp;*/</div><div></div><div>&nbsp; &nbsp; smp_mb();</div><div></div><div>&nbsp; &nbsp; fifo-&gt;out += len;</div><div></div><div>&nbsp; &nbsp; return len;</div><div>}</div><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div><div>9</div><div>10</div><div>11</div><div>12</div><div>13</div><div>14</div><div>15</div><div>16</div><div>17</div><div>18</div><div>19</div><div>20</div><div>21</div><div>22</div><div>23</div><div>24</div><div>25</div><div>26</div><div>27</div><div>28</div><div>29</div><div>30</div><div>31</div><div>32</div><div>认真读两遍吧，我也读了多次，每次总是有新发现，因为in, out和size的关系太巧妙了，竟然能利用上unsigned int回绕的特性。</div><div></div><div>原来，kfifo每次入队或出队，kfifo-&gt;in或kfifo-&gt;out只是简单地kfifo-&gt;in/kfifo-&gt;out += len，并没有对kfifo-&gt;size 进行取模运算。因此kfifo-&gt;in和kfifo-&gt;out总是一直增大，直到unsigned in最大值时，又会绕回到0这一起始端。但始终满足：</div><div></div><div>kfifo-&gt;in - kfifo-&gt;out &lt;= kfifo-&gt;size</div><div></div><div>即使kfifo-&gt;in回绕到了0的那一端，这个性质仍然是保持的。</div><div></div><div>对于给定的kfifo:</div><div></div><div>数据空间长度为：kfifo-&gt;in - kfifo-&gt;out</div><div></div><div>而剩余空间（可写入空间）长度为：kfifo-&gt;size - (kfifo-&gt;in - kfifo-&gt;out)</div><div></div><div>尽管kfifo-&gt;in和kfofo-&gt;out一直超过kfifo-&gt;size进行增长，但它对应在kfifo-&gt;buffer空间的下标却是如下：</div><div></div><div>kfifo-&gt;in % kfifo-&gt;size (i.e. kfifo-&gt;in &amp; (kfifo-&gt;size - 1))</div><div></div><div>kfifo-&gt;out % kfifo-&gt;size (i.e. kfifo-&gt;out &amp; (kfifo-&gt;size - 1))</div><div></div><div>往kfifo里面写一块数据时，数据空间、写入空间和kfifo-&gt;size的关系如果满足：</div><div></div><div>kfifo-&gt;in % size + len &gt; size</div><div></div><div>那就要做写拆分了，见下图：</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; kfifo_put（写）空间开始地址</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;\_/</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |XXXXXXXXXX</div><div>XXXXXXXX|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</div><div>+--------------------------------------------------------------+</div><div>|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&lt;----------data----------&gt;|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |</div><div>+--------------------------------------------------------------+</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ^&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ^</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;out%size&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;in%size&nbsp; &nbsp; &nbsp;size</div><div>&nbsp; &nbsp; &nbsp; &nbsp; ^</div><div>&nbsp; &nbsp; &nbsp; &nbsp; |</div><div>&nbsp; &nbsp; &nbsp; 写空间结束地址&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</div><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div><div>9</div><div>10</div><div>11</div><div>12</div><div>13</div><div>14</div><div>第一块当然是: [kfifo-&gt;in % kfifo-&gt;size, kfifo-&gt;size]&nbsp;</div><div>第二块当然是：[0, len - (kfifo-&gt;size - kfifo-&gt;in % kfifo-&gt;size)]</div><div></div><div>下面是代码，细细体味吧：</div><div></div><div>/* first put the data starting from fifo-&gt;in to buffer end */&nbsp; &nbsp;</div><div>l = min(len, fifo-&gt;size - (fifo-&gt;in &amp; (fifo-&gt;size - 1)));&nbsp; &nbsp;</div><div>memcpy(fifo-&gt;buffer + (fifo-&gt;in &amp; (fifo-&gt;size - 1)), buffer, l);&nbsp; &nbsp;</div><div></div><div>/* then put the rest (if any) at the beginning of the buffer */&nbsp; &nbsp;</div><div>memcpy(fifo-&gt;buffer, buffer + l, len - l);&nbsp;&nbsp;</div><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>对于kfifo_get过程，也是类似的，请各位自行分析。</div><div></div><div>kfifo_get和kfifo_put无锁并发操作</div><div>计算机科学家已经证明，当只有一个读经程和一个写线程并发操作时，不需要任何额外的锁，就可以确保是线程安全的，也即kfifo使用了无锁编程技术，以提高kernel的并发。</div><div></div><div>kfifo使用in和out两个指针来描述写入和读取游标，对于写入操作，只更新in指针，而读取操作，只更新out指针，可谓井水不犯河水，示意图如下：</div><div></div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&lt;--写入--&gt;|</div><div>+--------------------------------------------------------------+</div><div>|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&lt;----------data-----&gt;|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|</div><div>+--------------------------------------------------------------+</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&lt;--读取--&gt;|</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;^</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|</div><div>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;in&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; size</div><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><div>6</div><div>7</div><div>8</div><div>为了避免读者看到写者预计写入，但实际没有写入数据的空间，写者必须保证以下的写入顺序：</div><div></div><div>往[kfifo-&gt;in, kfifo-&gt;in + len]空间写入数据</div><div>更新kfifo-&gt;in指针为 kfifo-&gt;in + len</div><div>在操作1完成时，读者是还没有看到写入的信息的，因为kfifo-&gt;in没有变化，认为读者还没有开始写操作，只有更新kfifo-&gt;in之后，读者才能看到。</div><div></div><div>那么如何保证1必须在2之前完成，秘密就是使用内存屏障：smp_mb()，smp_rmb(), smp_wmb()，来保证对方观察到的内存操作顺序。</div><div></div><div>总结</div><div>读完kfifo代码，令我想起那首诗&#8220;众里寻他千百度，默然回首，那人正在灯火阑珊处&#8221;。不知你是否和我一样，总想追求简洁，高质量和可读性的代码，当用尽各种方法，江郞才尽之时，才发现Linux kernel里面的代码就是我们寻找和学习的对象</div><div>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</div><div>版权声明：本文为CSDN博主「海枫」的原创文章，遵循 CC 4.0 BY-SA 版权协议，转载请附上原文出处链接及本声明。</div><div>原文链接：https://blog.csdn.net/linyt/article/details/53355355</div><img src ="http://www.cppblog.com/markqian86/aggbug/217256.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-23 16:38 <a href="http://www.cppblog.com/markqian86/archive/2020/04/23/217256.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++对象模型</title><link>http://www.cppblog.com/markqian86/archive/2020/04/23/217255.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 23 Apr 2020 07:56:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/23/217255.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217255.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/23/217255.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217255.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217255.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 何为C++对象模型？C++对象模型可以概括为以下2部分：1.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;语言中直接支持面向对象程序设计的部分2.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;对于各种支持的底层实现机制语言中直接支持面向对象程序设计的部分，如构造函数、析构函数、虚函数、继承（单继承、...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2020/04/23/217255.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/217255.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-23 15:56 <a href="http://www.cppblog.com/markqian86/archive/2020/04/23/217255.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>EAGAIN、EWOULDBLOCK、EINTR</title><link>http://www.cppblog.com/markqian86/archive/2020/04/22/217253.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Wed, 22 Apr 2020 08:09:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/22/217253.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217253.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/22/217253.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217253.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217253.html</trackback:ping><description><![CDATA[<div></div><div>#define EAGAIN 11 /* Try again */</div><div>&nbsp;</div><div>#define EINTR 4 /* Interrupted system call */</div><div>&nbsp;</div><div>#define EWOULDBLOCK EAGAIN /* Operation would block */</div><div>EWOULDBLOCK用于非阻塞模式，不需要重新读或者写</div><div></div><div>EINTR指被中断唤醒，需要重新读/写</div><div></div><div>&nbsp;</div><div></div><div>在Linux环境下开发经常会碰到很多错误(设置errno)，其中EAGAIN是其中比较常见的一个错误（比如用在非阻塞操作中）。</div><div></div><div>从字面上来看，是提示在试一次。这个错误经常出现在当应用程序进行一些非阻塞（non-blocking）操作（对文件或socket）的时候。例如，以O_NONBLOCK的标记打开文件/socket/FIFO，如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回，read函数会返回一个错误EAGAIN，提示你的应用程序现在没有数据可读请稍后再试。</div><div></div><div>又例如，当一个系统调用(比如fork)因为没有足够的资源（比如虚拟内存）而执行失败，返回EAGAIN提示其在调用一次（也许下次就能成功）。</div><div></div><div>linux-非阻塞socket编程处理EAGAIN错误</div><div></div><div>在linux进行非阻塞的socket接受数据时经常出现Resource temporarily unavailable，errno代码为11（EAGAIN），这是什么意思？</div><div></div><div>这表明你在非阻塞模式下调用了阻塞操作，在该操作没有完成就返回这个错误，这个错误不会破坏socket的同步，不用管它，下次循环接着recv就可以。对非阻塞socket而言，EAGAIN不是一种错误。在VxWorks和Windows上，EAGAIN的名字叫做EWOULDBLOCK。</div><div><br /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">EAGAIN、EWOULDBLOCK、EINTR与非阻塞 长连接</span><div id="appShareOpt" style="outline: 0px; margin: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;"></div><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">EWOULDBLOCK用于非阻塞模式，不需要重新读或者写</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">EINTR指操作被中断唤醒，需要重新读/写</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">在Linux环境下开发经常会碰到很多错误(设置errno)，其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中)。</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">从字面上来看，是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。例如，以 O_NONBLOCK的标志打开文件/socket/FIFO，如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返 回，read函数会返回一个错误EAGAIN，提示你的应用程序现在没有数据可读请稍后再试。</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">又例如，当一个系统调用(比如fork)因为没有足够的资源(比如虚拟内存)而执行失败，返回EAGAIN提示其再调用一次(也许下次就能成功)。</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">Linux - 非阻塞socket编程处理EAGAIN错误</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">在linux进行非阻塞的socket接收数据时经常出现Resource temporarily unavailable，errno代码为11(EAGAIN)，这是什么意思？</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">这表明你在非阻塞模式下调用了阻塞操作，在该操作没有完成就返回这个错误，这个错误不会破坏socket的同步，不用管它，下次循环接着recv就可以。 对非阻塞socket而言，EAGAIN不是一种错误。在VxWorks和Windows上，EAGAIN的名字叫做EWOULDBLOCK。</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;">另外，如果出现EINTR即errno为4，错误描述Interrupted system call，操作也应该继续。</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; background-color: #ffffff; color: #666666; font-family: 宋体, Arial; font-size: 16px; line-height: 26px;" /><p style="outline: 0px; padding: 0px; font-family: &quot;Microsoft YaHei&quot;, &quot;SF Pro Display&quot;, Roboto, Noto, Arial, &quot;PingFang SC&quot;, sans-serif; box-sizing: border-box; font-size: 16px; color: #4d4d4d; line-height: 26px; margin: 0px 0px 16px; overflow-x: auto; word-wrap: break-word; background-color: #ffffff;"><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #666666; font-family: 宋体, Arial; line-height: 26px;">最后，如果recv的返回值为0，那表明连接已经断开，我们的接收操作也应该结束。</span></p><p style="outline: 0px; padding: 0px; font-family: &quot;Microsoft YaHei&quot;, &quot;SF Pro Display&quot;, Roboto, Noto, Arial, &quot;PingFang SC&quot;, sans-serif; box-sizing: border-box; font-size: 16px; color: #4d4d4d; line-height: 26px; margin: 0px 0px 16px; overflow-x: auto; word-wrap: break-word; background-color: #ffffff;"><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #666666; font-family: 宋体, Arial; line-height: 26px;"><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word;" /></span></p><p style="outline: 0px; padding: 0px; font-family: &quot;Microsoft YaHei&quot;, &quot;SF Pro Display&quot;, Roboto, Noto, Arial, &quot;PingFang SC&quot;, sans-serif; box-sizing: border-box; font-size: 16px; color: #4d4d4d; line-height: 26px; margin: 0px 0px 16px; overflow-x: auto; word-wrap: break-word; background-color: #ffffff;"><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; font-weight: 700; word-wrap: break-word;"><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #ff0000;">另外附一下几个问题：</span></span></p><p style="outline: 0px; padding: 0px; font-family: &quot;Microsoft YaHei&quot;, &quot;SF Pro Display&quot;, Roboto, Noto, Arial, &quot;PingFang SC&quot;, sans-serif; box-sizing: border-box; font-size: 16px; color: #4d4d4d; line-height: 26px; margin: 0px 0px 16px; overflow-x: auto; word-wrap: break-word; background-color: #ffffff;"><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;">1、阻塞模式与非阻塞模式下recv的返回值各代表什么意思？有没有 区别？（就我目前了解阻塞与非阻塞recv返回值没有区分，都是 &lt;0：出错，=0：连接关闭，&gt;0接收到数据大小，特别：返回 值 &lt;0时并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况 下认为连接是正常的，继续接收。只是阻塞模式下recv会阻塞着接收数据，非阻塞模式下如果没有数据会返回，不会阻塞着读，因此需要 循环读取</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;">2、阻塞模式与非阻塞模式下write的返回值各代表什么意思？ 有没有区别？</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;">阻塞与非阻塞write返回值没有区分，都是 &lt;0：出错，=0：连接关闭，&gt;0发送数据大小，特别：返回值 &lt;0时并且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的， 继续发送。只是阻塞模式下write会阻塞着发送数据，非阻塞模式下如果暂时无法发送数据会返回，不会阻塞着 write，因此需要循环发送</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;">3、阻塞模式下read返回 值 &lt; 0 &amp;&amp; errno != EINTR &amp;&amp; errno != EWOULDBLOCK &amp; amp;&amp; errno != EAGAIN时，连接异常，需要关闭，read返回值 &lt; 0 &amp;&amp; amp; (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)时表示没有数据， 需要继续接收，如果返回值大于0表示接送到数据。&nbsp;</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;">非阻塞模式下read返回值 &lt; 0表示没有数据，= 0表示 连接断开，&gt; 0表示接收到数据。&nbsp;</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;">这2种模式下的返回值是不是这么理解，有没有跟详细的理解或跟准确的 说明？&nbsp;</span><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><br style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;" /><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; color: #505050; font-family: 宋体, &quot;Arial Narrow&quot;, arial, serif; font-size: 14px; line-height: 28px;">4、阻塞模式与非阻塞模式下是否send返回 值 &lt; 0 &amp;&amp; (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) 表示暂时发送失败，需要重试，如果send返回值 &lt;= 0, &amp;&amp; errno != EINTR &amp;&amp; amp; errno != EWOULDBLOCK &amp;&amp; errno != EAGAIN时，连接异常，需要关闭，如果send返回 值 &gt; 0则表示发送了数据？send的返回值是否这么理解，阻塞模式与非阻塞模式下send返回值=0是否都是发送失败，还是那个模式下表示暂时 不可发送，需要 重发？</span></p></div><img src ="http://www.cppblog.com/markqian86/aggbug/217253.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-22 16:09 <a href="http://www.cppblog.com/markqian86/archive/2020/04/22/217253.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>理解 Memory barrier（内存屏障）无锁环形队列</title><link>http://www.cppblog.com/markqian86/archive/2020/04/21/217249.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Tue, 21 Apr 2020 08:49:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/21/217249.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217249.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/21/217249.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217249.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217249.html</trackback:ping><description><![CDATA[<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">http://name5566.com/4535.html</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">&nbsp;</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">http://wizmann.tk/linux-lockless-llist.html</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><code>typeof</code>和<code>sizeof</code>类似，sizeof求的是变量/类型的大小，而typeof是求变量/类型的数据类型。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">typeof在#define中的应用很多，例如：</p><div style="font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><pre style="white-space: pre-wrap; word-wrap: break-word; margin-top: 0px; margin-bottom: 0px; margin-left: 22px; font-size: 1em;"><span eye-protector-processed"="">#define max(a,b) \ <span eye-protector-processed"="">   ({ typeof (a) _a = (a); \ <span eye-protector-processed"="">       typeof (b) _b = (b); \ <span eye-protector-processed"="">     _a &gt; _b ? _a : _b; }) </span></span></span></span></pre></div><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><code>typeof(a)</code>获得了<code>a</code>的类型，声明了一个同类型的<code>_a</code>变量。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">p.s. 上面是一个安全的用<code>#define</code>实现的<code>max</code>函数。</p><h3>ACCESS_ONCE</h3><div style="font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><pre style="white-space: pre-wrap; word-wrap: break-word; margin-top: 0px; margin-bottom: 0px; margin-left: 22px; font-size: 1em;"><span eye-protector-processed"="">#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&amp;(x))； </span></pre></div><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><code>ACCESS_ONCE</code>使用了一个类型转换，使用<code>volatile</code>修饰&nbsp;<code>x</code>。避免编译器优化带来的潜在岐义。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">&nbsp;</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">参考文献列表：<br /><a href="http://en.wikipedia.org/wiki/Memory_barrier" style="text-decoration-line: none; color: #1d58d1;">http://en.wikipedia.org/wiki/Memory_barrier</a><br /><a href="http://en.wikipedia.org/wiki/Out-of-order_execution" style="text-decoration-line: none; color: #1d58d1;">http://en.wikipedia.org/wiki/Out-of-order_execution</a><br /><a href="https://www.kernel.org/doc/Documentation/memory-barriers.txt" style="text-decoration-line: none; color: #1d58d1;">https://www.kernel.org/doc/Documentation/memory-barriers.txt</a></p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">本文例子均在 Linux（g++）下验证通过，CPU 为 X86-64 处理器架构。所有罗列的 Linux 内核代码也均在（或只在）X86-64 下有效。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">本文首先通过范例（以及内核代码）来解释 Memory barrier，然后介绍一个利用 Memory barrier 实现的无锁环形缓冲区。</p><h4>Memory barrier 简介</h4><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致，这就是内存乱序访问。内存乱序访问行为出现的理由是为了提升程序运行时的性能。内存乱序访问主要发生在两个阶段：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li style="list-style-type: decimal;">编译时，编译器优化导致内存乱序访问（指令重排）</li><li style="list-style-type: decimal;">运行时，<span style="color: #ff0000;">多 CPU</span>&nbsp;间交互引起内存乱序访问</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">Memory barrier 能够让 CPU 或编译器在内存访问上有序。一个 Memory barrier 之前的内存访问操作必定先于其之后的完成。<span style="color: #ff0000;">Memory barrier 包括两类：</span></p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li style="list-style-type: decimal;"><span style="color: #ff0000;">编译器 barrier</span></li><li style="list-style-type: decimal;"><span style="color: #ff0000;">CPU Memory barrier</span></li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">很多时候，编译器和 CPU 引起内存乱序访问不会带来什么问题，但一些特殊情况下，程序逻辑的正确性依赖于内存访问顺序，这时候内存乱序访问会带来逻辑上的错误，例如：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">// thread 1</li><li style="list-style-type: decimal;">while&nbsp;(!ok);</li><li style="list-style-type: decimal;">do(x);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">// thread 2</li><li style="list-style-type: decimal;">x&nbsp;=&nbsp;42;</li><li style="list-style-type: decimal;">ok&nbsp;=&nbsp;1;</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">此段代码中，ok 初始化为 0，线程 1 等待 ok 被设置为 1 后执行 do 函数。假如说，线程 2 对内存的写操作乱序执行，也就是 x 赋值后于 ok 赋值完成，那么 do 函数接受的实参就很可能出乎程序员的意料，不为 42。</p><h4>编译时内存乱序访问</h4><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">在编译时，编译器对代码做出优化时可能改变实际执行指令的顺序（例如 gcc 下 O2 或 O3 都会改变实际执行指令的顺序）：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">// test.cpp</li><li style="list-style-type: decimal;">int&nbsp;x,&nbsp;y,&nbsp;r;</li><li style="list-style-type: decimal;">void&nbsp;f()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">x&nbsp;=&nbsp;r;</li><li style="list-style-type: decimal;">y&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">}</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">编译器优化的结果可能导致 y = 1 在 x = r 之前执行完成。首先直接编译此源文件：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">g++&nbsp;-S test.cpp</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">得到相关的汇编代码如下：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">movl r(%rip),&nbsp;%eax</li><li style="list-style-type: decimal;">movl&nbsp;%eax,&nbsp;x(%rip)</li><li style="list-style-type: decimal;">movl $1,&nbsp;y(%rip)</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">这里我们看到，x = r 和 y = 1 并没有乱序。现使用优化选项 O2（或 O3）编译上面的代码（g++ -O2 -S test.cpp），生成汇编代码如下：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">movl r(%rip),&nbsp;%eax</li><li style="list-style-type: decimal;">movl $1,&nbsp;y(%rip)</li><li style="list-style-type: decimal;">movl&nbsp;%eax,&nbsp;x(%rip)</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">我们可以清楚的看到经过编译器优化之后 movl $1, y(%rip) 先于 movl %eax, x(%rip) 执行。避免编译时内存乱序访问的办法就是使用编译器 barrier（又叫优化 barrier）。Linux 内核提供函数 barrier() 用于让编译器保证其之前的内存访问先于其之后的完成。<span style="color: #ff0000;">内核实现 barrier() 如下（X86-64 架构）：</span></p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;"><span style="color: #ff0000;">#define&nbsp;barrier()&nbsp;__asm__ __volatile__(""&nbsp;:::&nbsp;"memory")</span></li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">现在把此编译器 barrier 加入代码中：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">int&nbsp;x,&nbsp;y,&nbsp;r;</li><li style="list-style-type: decimal;">void&nbsp;f()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">x&nbsp;=&nbsp;r;</li><li style="list-style-type: decimal;">__asm__ __volatile__(""&nbsp;:::&nbsp;"memory");</li><li style="list-style-type: decimal;">y&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">}</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">这样就避免了编译器优化带来的内存乱序访问的问题了（如果有兴趣可以再看看编译之后的汇编代码）。本例中，我们还可以使用&nbsp;<span style="color: #ff0000;">volatile 这个关键字来避免编译时内存乱序访问（而无法避免后面要说的运行时内存乱序访问）</span>。volatile 关键字能够让相关的变量之间在内存访问上避免乱序，这里可以修改 x 和 y 的定义来解决问题：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">volatile&nbsp;int&nbsp;x,&nbsp;y;</li><li style="list-style-type: decimal;">int&nbsp;r;</li><li style="list-style-type: decimal;">void&nbsp;f()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">x&nbsp;=&nbsp;r;</li><li style="list-style-type: decimal;">y&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">}</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">现加上了 volatile 关键字，这使得 x 相对于 y、y 相对于 x 在内存访问上有序。在 Linux 内核中，提供了一个<span style="color: #ff0000;">宏 ACCESS_ONCE 来避免编译器对于连续的 ACCESS_ONCE 实例进行指令重排</span>。其实 ACCESS_ONCE 实现源码如下：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">#define&nbsp;ACCESS_ONCE(x)&nbsp;(*(volatile&nbsp;typeof(x)&nbsp;*)&amp;(x))</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">此代码只是将变量 x 转换为 volatile 的而已。现在我们就有了第三个修改方案：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">int&nbsp;x,&nbsp;y,&nbsp;r;</li><li style="list-style-type: decimal;">void&nbsp;f()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">ACCESS_ONCE(x)&nbsp;=&nbsp;r;</li><li style="list-style-type: decimal;">ACCESS_ONCE(y)&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">}</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">到此基本上就阐述完了我们的编译时内存乱序访问的问题。下面开始介绍运行时内存乱序访问。</p><h4>运行时内存乱序访问</h4><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">在运行时，CPU 虽然会乱序执行指令，但是在<span style="color: #ff0000;">单个 CPU 的上</span>，硬件能够保证程序执行时所有的内存访问操作看起来像是按程序代码编写的顺序执行的，这时候&nbsp;<span style="color: #ff0000;">Memory barrier 没有必要使用</span>（不考虑编译器优化的情况下）。这里我们了解一下 CPU 乱序执行的行为。在乱序执行时，<span style="color: #ff0000;"><strong>一个处理器真正执行指令的顺序由可用的输入数据决定，而非程序员编写的顺序。</strong></span><br />早期的处理器为有序处理器（In-order processors），有序处理器处理指令通常有以下几步：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li style="list-style-type: decimal;">指令获取</li><li style="list-style-type: decimal;">如果指令的输入操作对象（input operands）可用（例如已经在寄存器中了），则将此指令分发到适当的功能单元中。如果一个或者多个操作对象不可用（通常是由于需要从内存中获取），则处理器会等待直到它们可用</li><li style="list-style-type: decimal;">指令被适当的功能单元执行</li><li style="list-style-type: decimal;">功能单元将结果写回寄存器堆（Register file，一个 CPU 中的一组寄存器）</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">相比之下，乱序处理器（Out-of-order processors）处理指令通常有以下几步：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li style="list-style-type: decimal;">指令获取</li><li style="list-style-type: decimal;">指令被分发到指令队列</li><li style="list-style-type: decimal;">指令在指令队列中等待，直到输入操作对象可用（一旦输入操作对象可用，指令就可以离开队列，<span style="color: #ff0000;">即便更早的指令未被执行</span>）</li><li style="list-style-type: decimal;">指令被分配到适当的功能单元并执行</li><li style="list-style-type: decimal;">执行结果被放入队列（而不立即写入寄存器堆）</li><li style="list-style-type: decimal;">只有所有更早请求执行的指令的执行结果被写入寄存器堆后，指令执行的结果才被写入寄存器堆（<span style="color: #ff0000;">执行结果重排序，让执行看起来是有序的</span>）</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">从上面的执行过程可以看出，乱序执行相比有序执行能够避免等待不可用的操作对象（有序执行的第二步）从而提高了效率。现代的机器上，处理器运行的速度比内存快很多，有序处理器花在等待可用数据的时间里已经可以处理大量指令了。<br />现在思考一下乱序处理器处理指令的过程，我们能得到几个结论：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li style="list-style-type: decimal;">对于单个 CPU 指令获取是有序的（通过队列实现）</li><li style="list-style-type: decimal;">对于单个 CPU 指令执行结果也是有序返回寄存器堆的（通过队列实现）</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">由此可知，<span style="color: #ff0000;">在单 CPU 上，不考虑编译器优化导致乱序的前提下，多线程执行不存在内存乱序访问的问题</span>。我们从内核源码也可以得到类似的结论（代码不完全的摘录）：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">#ifdef&nbsp;CONFIG_SMP</li><li style="list-style-type: decimal;">#define&nbsp;smp_mb()&nbsp;mb()</li><li style="list-style-type: decimal;">#else</li><li style="list-style-type: decimal;">#define&nbsp;smp_mb()&nbsp;barrier()</li><li style="list-style-type: decimal;">#endif</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">这里可以看到，如果是 SMP 则使用 mb，mb 被定义为 CPU Memory barrier（后面会讲到），而非 SMP 时，直接使用编译器 barrier。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">在多 CPU 的机器上，问题又不一样了。每个 CPU 都存在 cache（cache 主要是为了弥补 CPU 和内存之间较慢的访问速度），当一个特定数据第一次被特定一个 CPU 获取时，此数据显然不在 CPU 的 cache 中（这就是 cache miss）。此 cache miss 意味着 CPU 需要从内存中获取数据（这个过程需要 CPU 等待数百个周期），此数据将被加载到 CPU 的 cache 中，这样后续就能直接从 cache 上快速访问。当某个 CPU 进行写操作时，它必须确保其他的 CPU 已经将此数据从它们的 cache 中移除（以便保证一致性），只有在移除操作完成后此 CPU 才能安全的修改数据。显然，存在多个 cache 时，我们必须通过一个 cache 一致性协议来避免数据不一致的问题，而这个通讯的过程就可能导致乱序访问的出现，也就是这里说的运行时内存乱序访问。这里不再深入讨论整个细节，这是一个比较复杂的问题，有兴趣可以研究&nbsp;<a href="http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf" style="text-decoration-line: none; color: #1d58d1;">http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf</a>&nbsp;一文，其详细的分析了整个过程。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">现在通过一个例子来说明多 CPU 下内存乱序访问：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">// test2.cpp</li><li style="list-style-type: decimal;">#include&nbsp;&lt;pthread.h&gt;</li><li style="list-style-type: decimal;">#include&nbsp;&lt;assert.h&gt;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">// -------------------</li><li style="list-style-type: decimal;">int&nbsp;cpu_thread1&nbsp;=&nbsp;0;</li><li style="list-style-type: decimal;">int&nbsp;cpu_thread2&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">volatile&nbsp;int&nbsp;x,&nbsp;y,&nbsp;r1,&nbsp;r2;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">void&nbsp;start()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">x&nbsp;=&nbsp;y&nbsp;=&nbsp;r1&nbsp;=&nbsp;r2&nbsp;=&nbsp;0;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">void&nbsp;end()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">assert(!(r1&nbsp;==&nbsp;0&nbsp;&amp;&amp;&nbsp;r2&nbsp;==&nbsp;0));</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">void&nbsp;run1()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">x&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">r1&nbsp;=&nbsp;y;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">void&nbsp;run2()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">y&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">r2&nbsp;=&nbsp;x;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">// -------------------</li><li style="list-style-type: decimal;">static&nbsp;pthread_barrier_t&nbsp;barrier_start;</li><li style="list-style-type: decimal;">static&nbsp;pthread_barrier_t&nbsp;barrier_end;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">static&nbsp;void*&nbsp;thread1(void*)</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">while&nbsp;(1)&nbsp;{</li><li style="list-style-type: decimal;">pthread_barrier_wait(&amp;barrier_start);</li><li style="list-style-type: decimal;">run1();</li><li style="list-style-type: decimal;">pthread_barrier_wait(&amp;barrier_end);</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">return&nbsp;NULL;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">static&nbsp;void*&nbsp;thread2(void*)</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">while&nbsp;(1)&nbsp;{</li><li style="list-style-type: decimal;">pthread_barrier_wait(&amp;barrier_start);</li><li style="list-style-type: decimal;">run2();</li><li style="list-style-type: decimal;">pthread_barrier_wait(&amp;barrier_end);</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">return&nbsp;NULL;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">int&nbsp;main()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">assert(<span style="color: #ff0000;">pthread_barrier_init(&amp;barrier_start,&nbsp;NULL,&nbsp;3)&nbsp;==&nbsp;0);</span></li><li style="list-style-type: decimal;">assert(pthread_barrier_init(&amp;barrier_end,&nbsp;NULL,&nbsp;3)&nbsp;==&nbsp;0);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">pthread_t&nbsp;t1;</li><li style="list-style-type: decimal;">pthread_t&nbsp;t2;</li><li style="list-style-type: decimal;">assert(pthread_create(&amp;t1,&nbsp;NULL,&nbsp;thread1,&nbsp;NULL)&nbsp;==&nbsp;0);</li><li style="list-style-type: decimal;">assert(pthread_create(&amp;t2,&nbsp;NULL,&nbsp;thread2,&nbsp;NULL)&nbsp;==&nbsp;0);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">cpu_set_t&nbsp;cs;</li><li style="list-style-type: decimal;"><span style="color: #ff0000;">CPU_ZERO(&amp;cs);</span></li><li style="list-style-type: decimal;">CPU_SET(cpu_thread1,&nbsp;&amp;cs);</li><li style="list-style-type: decimal;">assert(<span style="color: #ff0000;">pthread_setaffinity_np(t1,&nbsp;sizeof(cs),&nbsp;&amp;cs)&nbsp;==&nbsp;0);</span></li><li style="list-style-type: decimal;">CPU_ZERO(&amp;cs);</li><li style="list-style-type: decimal;">CPU_SET(cpu_thread2,&nbsp;&amp;cs);</li><li style="list-style-type: decimal;">assert(pthread_setaffinity_np(t2,&nbsp;sizeof(cs),&nbsp;&amp;cs)&nbsp;==&nbsp;0);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">while&nbsp;(1)&nbsp;{</li><li style="list-style-type: decimal;">start();</li><li style="list-style-type: decimal;"><span style="color: #ff0000;">pthread_barrier_wait(&amp;barrier_start);</span></li><li style="list-style-type: decimal;">pthread_barrier_wait(&amp;barrier_end);</li><li style="list-style-type: decimal;">end();</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">return&nbsp;0;</li><li style="list-style-type: decimal;">}</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">这里创建了两个线程来运行测试代码（需要测试的代码将放置在 run 函数中）。我使用了 pthread barrier（区别于本文讨论的 Memory barrier）主要为了让两个子线程能够同时运行它们的 run 函数。此段代码不停的尝试同时运行两个线程的 run 函数，以便得出我们期望的结果。在每次运行 run 函数前会调用一次 start 函数（进行数据初始化），run 运行后会调用一次 end 函数（进行结果检查）。run1 和 run2 两个函数运行在哪个 CPU 上则通过 cpu_thread1 和 cpu_thread2 两个变量控制。<br />先编译此程序：g++ -lpthread -o test2 test2.cpp（这里未优化，目的是为了避免编译器优化的干扰）。需要注意的是，<span style="color: #ff0000;">两个线程运行在两个不同的 CPU 上（CPU 0 和 CPU 1）</span>。只要内存不出现乱序访问，那么 r1 和 r2 不可能同时为 0，因此断言失败表示存在内存乱序访问。编译之后运行此程序，会发现存在一定概率导致断言失败。为了进一步说明问题，我们把 cpu_thread2 的值改为 0，换而言之就是让两个线程跑在同一个 CPU 下，再运行程序发现断言不再失败。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">最后，我们使用 CPU Memory barrier 来解决内存乱序访问的问题（X86-64 架构下）：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">int&nbsp;cpu_thread1&nbsp;=&nbsp;0;</li><li style="list-style-type: decimal;">int&nbsp;cpu_thread2&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">void&nbsp;run1()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">x&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">__asm__ __volatile__("mfence"&nbsp;:::&nbsp;"memory");</li><li style="list-style-type: decimal;">r1&nbsp;=&nbsp;y;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">void&nbsp;run2()</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">y&nbsp;=&nbsp;1;</li><li style="list-style-type: decimal;">__asm__ __volatile__("mfence"&nbsp;:::&nbsp;"memory");</li><li style="list-style-type: decimal;">r2&nbsp;=&nbsp;x;</li><li style="list-style-type: decimal;">}</li></ol><h4>准备使用 Memory barrier</h4><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><span style="font-size: 16px;"><strong><span style="color: #ff0000;">Memory barrier 常用场合包括：</span></strong></span></p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li style="list-style-type: decimal;"><span style="font-size: 16px;"><strong><span style="color: #ff0000;">实现同步原语（synchronization primitives）</span></strong></span></li><li style="list-style-type: decimal;"><span style="font-size: 16px;"><strong><span style="color: #ff0000;">实现无锁数据结构（lock-free data structures）</span></strong></span></li><li style="list-style-type: decimal;"><span style="font-size: 16px;"><strong><span style="color: #ff0000;">驱动程序</span></strong></span></li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">实际的应用程序开发中，开发者可能完全不知道 Memory barrier 就可以开发正确的多线程程序，这主要是因为<span style="color: #ff0000;">各种同步机制中已经隐含了 Memory barrier（</span>但和实际的 Memory barrier 有细微差别），这就使得不直接使用 Memory barrier 不会存在任何问题。但是<span style="color: #ff0000;">如果你希望编写诸如无锁数据结构，那么 Memory barrier 还是很有用的。</span></p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">通常来说，在单个 CPU 上，存在依赖的内存访问有序：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">Q&nbsp;=&nbsp;P;</li><li style="list-style-type: decimal;">D&nbsp;=&nbsp;*Q;</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">这里内存操作有序。然而在 Alpha CPU 上，存在依赖的内存读取操作不一定有序，需要使用数据依赖 barrier（由于 Alpha 不常见，这里就不详细解释了）。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">在 Linux 内核中，除了前面说到的编译器 barrier &#8212; barrier() 和 ACCESS_ONCE()，还有 CPU Memory barrier：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li style="list-style-type: decimal;"><span style="color: #ff0000;">通用 barrier，保证读写操作有序的，mb() 和 smp_mb()</span></li><li style="list-style-type: decimal;"><span style="color: #ff0000;">写操作 barrier，仅保证写操作有序的，wmb() 和 smp_wmb()</span></li><li style="list-style-type: decimal;"><span style="color: #ff0000;">读操作 barrier，仅保证读操作有序的，rmb() 和 smp_rmb()</span></li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">注意，所有的 CPU Memory barrier（除了数据依赖 barrier 之外）都隐含了编译器 barrier。这里的&nbsp;<span style="color: #ff0000;">smp 开头的 Memory barrier 会根据</span>配置在单处理器上直接使用编译器 barrier，而在 SMP 上才使用 CPU Memory barrier（也就是 mb()、wmb()、rmb()，回忆上面相关内核代码）。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">最后需要注意一点的是，CPU Memory barrier 中某些类型的 Memory barrier 需要成对使用，否则会出错，详细来说就是：一<span style="color: #ff0000;">个写操作 barrier 需要和读操作（或数据依赖）barrier 一起使用（当然，通用 barrier 也是可以的），反之依然。</span></p><h4>Memory barrier 的范例</h4><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">读内核代码进一步学习 Memory barrier 的使用。<br />Linux 内核实现的无锁（只有一个读线程和一个写线程时）环形缓冲区 kfifo 就使用到了 Memory barrier，实现源码如下：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li value="1" style="list-style-type: decimal;">/*</li><li style="list-style-type: decimal;">* A simple kernel FIFO implementation.</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* Copyright (C) 2004 Stelian Pop &lt;stelian@popies.net&gt;</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* This program is free software; you can redistribute it and/or modify</li><li style="list-style-type: decimal;">* it under the terms of the GNU General Public License as published by</li><li style="list-style-type: decimal;">* the Free Software Foundation; either version 2 of the License, or</li><li style="list-style-type: decimal;">* (at your option) any later version.</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* This program is distributed in the hope that it will be useful,</li><li style="list-style-type: decimal;">* but WITHOUT ANY WARRANTY; without even the implied warranty of</li><li style="list-style-type: decimal;">* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</li><li style="list-style-type: decimal;">* GNU General Public License for more details.</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* You should have received a copy of the GNU General Public License</li><li style="list-style-type: decimal;">* along with this program; if not, write to the Free Software</li><li style="list-style-type: decimal;">* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">#include&nbsp;&lt;linux/kernel.h&gt;</li><li style="list-style-type: decimal;">#include&nbsp;&lt;linux/module.h&gt;</li><li style="list-style-type: decimal;">#include&nbsp;&lt;linux/slab.h&gt;</li><li style="list-style-type: decimal;">#include&nbsp;&lt;linux/err.h&gt;</li><li style="list-style-type: decimal;">#include&nbsp;&lt;linux/kfifo.h&gt;</li><li style="list-style-type: decimal;">#include&nbsp;&lt;linux/log2.h&gt;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/**</li><li style="list-style-type: decimal;">* kfifo_init - allocates a new FIFO using a preallocated buffer</li><li style="list-style-type: decimal;">* @buffer: the preallocated buffer to be used.</li><li style="list-style-type: decimal;">* @size: the size of the internal buffer, this have to be a power of 2.</li><li style="list-style-type: decimal;">* @gfp_mask: get_free_pages mask, passed to kmalloc()</li><li style="list-style-type: decimal;">* @lock: the lock to be used to protect the fifo buffer</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* Do NOT pass the kfifo to kfifo_free() after use! Simply free the</li><li style="list-style-type: decimal;">* &amp;struct kfifo with kfree().</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">struct&nbsp;kfifo&nbsp;*kfifo_init(unsigned&nbsp;char&nbsp;*buffer,&nbsp;unsigned&nbsp;int&nbsp;size,</li><li style="list-style-type: decimal;">gfp_t&nbsp;gfp_mask,&nbsp;spinlock_t&nbsp;*lock)</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">struct&nbsp;kfifo&nbsp;*fifo;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/* size must be a power of 2 */</li><li style="list-style-type: decimal;">BUG_ON(!is_power_of_2(size));</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">fifo&nbsp;=&nbsp;kmalloc(sizeof(struct&nbsp;kfifo),&nbsp;gfp_mask);</li><li style="list-style-type: decimal;">if&nbsp;(!fifo)</li><li style="list-style-type: decimal;">return&nbsp;ERR_PTR(-ENOMEM);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">fifo-&gt;buffer&nbsp;=&nbsp;buffer;</li><li style="list-style-type: decimal;">fifo-&gt;size&nbsp;=&nbsp;size;</li><li style="list-style-type: decimal;">fifo-&gt;in&nbsp;=&nbsp;fifo-&gt;out&nbsp;=&nbsp;0;</li><li style="list-style-type: decimal;">fifo-&gt;lock&nbsp;=&nbsp;lock;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">return&nbsp;fifo;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">EXPORT_SYMBOL(kfifo_init);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/**</li><li style="list-style-type: decimal;">* kfifo_alloc - allocates a new FIFO and its internal buffer</li><li style="list-style-type: decimal;">* @size: the size of the internal buffer to be allocated.</li><li style="list-style-type: decimal;">* @gfp_mask: get_free_pages mask, passed to kmalloc()</li><li style="list-style-type: decimal;">* @lock: the lock to be used to protect the fifo buffer</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* The size will be rounded-up to a power of 2.</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">struct&nbsp;kfifo&nbsp;*kfifo_alloc(unsigned&nbsp;int&nbsp;size,&nbsp;gfp_t&nbsp;gfp_mask,&nbsp;spinlock_t&nbsp;*lock)</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">unsigned&nbsp;char&nbsp;*buffer;</li><li style="list-style-type: decimal;">struct&nbsp;kfifo&nbsp;*ret;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/*</li><li style="list-style-type: decimal;">* round up to the next power of 2, since our 'let the indices</li><li style="list-style-type: decimal;">* wrap' technique works only in this case.</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">if&nbsp;(!is_power_of_2(size))&nbsp;{</li><li style="list-style-type: decimal;">BUG_ON(size&nbsp;&gt;&nbsp;0x80000000);</li><li style="list-style-type: decimal;">size&nbsp;=&nbsp;roundup_pow_of_two(size);</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">buffer&nbsp;=&nbsp;kmalloc(size,&nbsp;gfp_mask);</li><li style="list-style-type: decimal;">if&nbsp;(!buffer)</li><li style="list-style-type: decimal;">return&nbsp;ERR_PTR(-ENOMEM);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">ret&nbsp;=&nbsp;kfifo_init(buffer,&nbsp;size,&nbsp;gfp_mask,&nbsp;lock);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">if&nbsp;(IS_ERR(ret))</li><li style="list-style-type: decimal;">kfree(buffer);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">return&nbsp;ret;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">EXPORT_SYMBOL(kfifo_alloc);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/**</li><li style="list-style-type: decimal;">* kfifo_free - frees the FIFO</li><li style="list-style-type: decimal;">* @fifo: the fifo to be freed.</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">void&nbsp;kfifo_free(struct&nbsp;kfifo&nbsp;*fifo)</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">kfree(fifo-&gt;buffer);</li><li style="list-style-type: decimal;">kfree(fifo);</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">EXPORT_SYMBOL(kfifo_free);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/**</li><li style="list-style-type: decimal;">* __kfifo_put - puts some data into the FIFO, no locking version</li><li style="list-style-type: decimal;">* @fifo: the fifo to be used.</li><li style="list-style-type: decimal;">* @buffer: the data to be added.</li><li style="list-style-type: decimal;">* @len: the length of the data to be added.</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* This function copies at most @len bytes from the @buffer into</li><li style="list-style-type: decimal;">* the FIFO depending on the free space, and returns the number of</li><li style="list-style-type: decimal;">* bytes copied.</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* Note that with only one concurrent reader and one concurrent</li><li style="list-style-type: decimal;">* writer, you don't need extra locking to use these functions.</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">unsigned&nbsp;int&nbsp;__kfifo_put(struct&nbsp;kfifo&nbsp;*fifo,</li><li style="list-style-type: decimal;">const&nbsp;unsigned&nbsp;char&nbsp;*buffer,&nbsp;unsigned&nbsp;int&nbsp;len)</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">unsigned&nbsp;int&nbsp;l;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">len&nbsp;=&nbsp;min(len,&nbsp;fifo-&gt;size&nbsp;-&nbsp;fifo-&gt;in&nbsp;+&nbsp;fifo-&gt;out);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/*</li><li style="list-style-type: decimal;">* Ensure that we sample the fifo-&gt;out index -before- we</li><li style="list-style-type: decimal;">* start putting bytes into the kfifo.</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">smp_mb();</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/* first put the data starting from fifo-&gt;in to buffer end */</li><li style="list-style-type: decimal;">l&nbsp;=&nbsp;min(len,&nbsp;fifo-&gt;size&nbsp;-&nbsp;(fifo-&gt;in&nbsp;&amp;&nbsp;(fifo-&gt;size&nbsp;-&nbsp;1)));</li><li style="list-style-type: decimal;">memcpy(fifo-&gt;buffer&nbsp;+&nbsp;(fifo-&gt;in&nbsp;&amp;&nbsp;(fifo-&gt;size&nbsp;-&nbsp;1)),&nbsp;buffer,&nbsp;l);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/* then put the rest (if any) at the beginning of the buffer */</li><li style="list-style-type: decimal;">memcpy(fifo-&gt;buffer,&nbsp;buffer&nbsp;+&nbsp;l,&nbsp;len&nbsp;-&nbsp;l);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/*</li><li style="list-style-type: decimal;">* Ensure that we add the bytes to the kfifo -before-</li><li style="list-style-type: decimal;">* we update the fifo-&gt;in index.</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">smp_wmb();</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">fifo-&gt;in&nbsp;+=&nbsp;len;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">return&nbsp;len;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">EXPORT_SYMBOL(__kfifo_put);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/**</li><li style="list-style-type: decimal;">* __kfifo_get - gets some data from the FIFO, no locking version</li><li style="list-style-type: decimal;">* @fifo: the fifo to be used.</li><li style="list-style-type: decimal;">* @buffer: where the data must be copied.</li><li style="list-style-type: decimal;">* @len: the size of the destination buffer.</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* This function copies at most @len bytes from the FIFO into the</li><li style="list-style-type: decimal;">* @buffer and returns the number of copied bytes.</li><li style="list-style-type: decimal;">*</li><li style="list-style-type: decimal;">* Note that with only one concurrent reader and one concurrent</li><li style="list-style-type: decimal;">* writer, you don't need extra locking to use these functions.</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">unsigned&nbsp;int&nbsp;__kfifo_get(struct&nbsp;kfifo&nbsp;*fifo,</li><li style="list-style-type: decimal;">unsigned&nbsp;char&nbsp;*buffer,&nbsp;unsigned&nbsp;int&nbsp;len)</li><li style="list-style-type: decimal;">{</li><li style="list-style-type: decimal;">unsigned&nbsp;int&nbsp;l;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">len&nbsp;=&nbsp;min(len,&nbsp;fifo-&gt;in&nbsp;-&nbsp;fifo-&gt;out);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/*</li><li style="list-style-type: decimal;">* Ensure that we sample the fifo-&gt;in index -before- we</li><li style="list-style-type: decimal;">* start removing bytes from the kfifo.</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">smp_rmb();</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/* first get the data from fifo-&gt;out until the end of the buffer */</li><li style="list-style-type: decimal;">l&nbsp;=&nbsp;min(len,&nbsp;fifo-&gt;size&nbsp;-&nbsp;(fifo-&gt;out&nbsp;&amp;&nbsp;(fifo-&gt;size&nbsp;-&nbsp;1)));</li><li style="list-style-type: decimal;">memcpy(buffer,&nbsp;fifo-&gt;buffer&nbsp;+&nbsp;(fifo-&gt;out&nbsp;&amp;&nbsp;(fifo-&gt;size&nbsp;-&nbsp;1)),&nbsp;l);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/* then get the rest (if any) from the beginning of the buffer */</li><li style="list-style-type: decimal;">memcpy(buffer&nbsp;+&nbsp;l,&nbsp;fifo-&gt;buffer,&nbsp;len&nbsp;-&nbsp;l);</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">/*</li><li style="list-style-type: decimal;">* Ensure that we remove the bytes from the kfifo -before-</li><li style="list-style-type: decimal;">* we update the fifo-&gt;out index.</li><li style="list-style-type: decimal;">*/</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">smp_mb();</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">fifo-&gt;out&nbsp;+=&nbsp;len;</li><li style="list-style-type: decimal;">&nbsp;</li><li style="list-style-type: decimal;">return&nbsp;len;</li><li style="list-style-type: decimal;">}</li><li style="list-style-type: decimal;">EXPORT_SYMBOL(__kfifo_get);</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">为了更好的理解上面的源码，这里顺带说一下此实现使用到的一些和本文主题无关的技巧：</p><ol style="padding-left: 40px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;"><li style="list-style-type: decimal;">使用与操作来求取环形缓冲区的下标，相比取余操作来求取下标的做法效率要高不少。使用与操作求取下标的前提是环形缓冲区的大小必须是 2 的 N 次方，换而言之就是说环形缓冲区的大小为一个仅有一个 1 的二进制数，那么 index &amp; (size &#8211; 1) 则为求取的下标（这不难理解）</li><li style="list-style-type: decimal;">使用了 in 和 out 两个索引且 in 和 out 是一直递增的（此做法比较巧妙），这样能够避免一些复杂的条件判断（某些实现下，in == out 时还无法区分缓冲区是空还是满）</li></ol><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">这里，索引 in 和 out 被两个线程访问。in 和 out 指明了缓冲区中实际数据的边界，也就是 in 和 out 同缓冲区数据存在访问上的顺序关系，由于未使用同步机制，那么保证顺序关系就需要使用到 Memory barrier 了。索引 in 和 out 都分别只被一个线程修改，而被两个线程读取。__kfifo_put 先通过 in 和 out 来确定可以向缓冲区中写入数据量的多少，这时，out 索引应该先被读取后才能真正的将用户 buffer 中的数据写入缓冲区，因此这里使用到了 smp_mb()，对应的，__kfifo_get 也使用 smp_mb() 来确保修改 out 索引之前缓冲区中数据已经被成功读取并写入用户 buffer 中了。对于 in 索引，在 __kfifo_put 中，通过 smp_wmb() 保证先向缓冲区写入数据后才修改 in 索引，由于这里只需要保证写入操作有序，故选用写操作 barrier，在 __kfifo_get 中，通过 smp_rmb() 保证先读取了 in 索引（这时候 in 索引用于确定缓冲区中实际存在多少可读数据）才开始读取缓冲区中数据（并写入用户 buffer 中），由于这里只需要保证读取操作有序，故选用读操作 barrier。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, &quot;Lucida Grande&quot;, Arial, Helvetica, sans-serif; font-size: 12px; background-color: #ffffff;">到这里，Memory barrier 就介绍完毕了。</p><img src ="http://www.cppblog.com/markqian86/aggbug/217249.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-21 16:49 <a href="http://www.cppblog.com/markqian86/archive/2020/04/21/217249.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>无锁环形队列，volatile和乱序执行</title><link>http://www.cppblog.com/markqian86/archive/2020/04/21/217248.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Tue, 21 Apr 2020 08:13:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/21/217248.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217248.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/21/217248.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217248.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217248.html</trackback:ping><description><![CDATA[<div>这里说的环形队列是一种内存通讯机制，本身这个机制和语言没有什么关系，不过上篇提到了volatile语法和acquire/release语义，就以这个机制做一个例子，C语言实现。这方面的内容涉及到一些现有的语言实现的东西&nbsp;</div><div></div><div>环形队列的数据结构是一个数组，简单起见我们认为通讯内容就是一个个int，则定义一个int数组和头尾索引：&nbsp;</div><div>int queue[SIZE];&nbsp;</div><div>int head;&nbsp;</div><div>int tail;&nbsp;</div><div>方便起见可以约定：head表示队列首元素的索引，tail表示队列尾元素后面的空位的索引，队列中最多可容纳SIZE-1个元素，因为这样可以head==tail表示队列空，head==(tail+1)%SIZE表示队列满，如果能容纳SIZE个元素，就不能区分这两种情况了&nbsp;</div><div></div><div>然后是读写：&nbsp;</div><div>ErrorType read(int *v)&nbsp;</div><div>{&nbsp;</div><div>&nbsp; &nbsp; if (head == tail)&nbsp;</div><div>&nbsp; &nbsp; {&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return EMPTY;&nbsp;</div><div>&nbsp; &nbsp; }&nbsp;</div><div>&nbsp; &nbsp; *v = queue[head];&nbsp;</div><div>&nbsp; &nbsp; head = (head + 1) % SIZE;&nbsp;</div><div>&nbsp; &nbsp; return SUCCESS;&nbsp;</div><div>}&nbsp;</div><div>ErrorType write(const int *v)&nbsp;</div><div>{&nbsp;</div><div>&nbsp; &nbsp; if (head == (tail + 1) % SIZE)&nbsp;</div><div>&nbsp; &nbsp; {&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; return FULL;&nbsp;</div><div>&nbsp; &nbsp; }&nbsp;</div><div>&nbsp; &nbsp; queue[tail] = *v;&nbsp;</div><div>&nbsp; &nbsp; tail = (tail + 1) % SIZE;&nbsp;</div><div>&nbsp; &nbsp; return SUCCESS;&nbsp;</div><div>}&nbsp;</div><div></div><div>假如有多个线程读写队列，则一般需要锁来实现同步，但是做一点点约束和修改，就能实现无锁：&nbsp;</div><div>1 队列只能单读单写&nbsp;</div><div>2 共享的数据用volatile修饰&nbsp;</div><div></div><div>可以就上面的代码简单分析下，read操作只会读写head，读tail和queue，反之write操作读写tail，读head，写queue，而head和tail都是正向增长的。这里的关键在于，head和tail使用int，它们的读写用一条机器指令即可完成，是原子的，在这个前提下，上述两个函数分别在两个线程执行时，无论怎么调度穿插，都不会产生冲突。例如，在读的时候，另一个线程正在写，由于是内容写完再修改tail的值，因此不会读到写了一半的数据，最多就是返回一个EMPTY错误，下次轮询的时候还能读到，反之写的时候如果有人在读，因为是读完内容才修改head，因此不会冲乱正在读的数据（当然，由于上面举例中queue的元素是int，所以不会出现单个元素不一致的情况，不过如果是结构体或一段数据就可能）。但若不是单读单写，就可能出问题了&nbsp;</div><div></div><div>然后分析下为什么数据要加volatile，估计很多人都会说，因为如果不加，编译器会优化变量到寄存器，比如write修改了tail的值，而read把tail优化到寄存器里，就一直以为队列还是空的。的确volatile能阻止编译器做这类优化，每次读取都会保证从内存重新读取到寄存器，但是就上面这个例子而言，不存在这个问题，read函数在被调用的时候，还是要从内存读一下tail，因为一般来说不可能在read退出后还给它把tail值保存在寄存器里，事实上只有当在一个函数的代码段中重复使用一个变量的时候，才做上面这种优化&nbsp;</div><div></div><div>这个例子里面用volatile，是为了执行的顺序性，根据上面的分析可以看到，除int的读写是原子外，这个无锁机制依赖于执行顺序，即读的时候先读，再改head，写的时候先写，再改tail。不少人可能觉得，我代码既然这么写了，那应该是按这个顺序执行，可惜这只是天真的想法，举例：&nbsp;</div><div>static int a;&nbsp;</div><div>static int b = 1;&nbsp;</div><div>int main()&nbsp;</div><div>{&nbsp;</div><div>&nbsp; &nbsp; a = b + 1;&nbsp;</div><div>&nbsp; &nbsp; b = 0;&nbsp;</div><div>&nbsp; &nbsp; return 0;&nbsp;</div><div>}&nbsp;</div><div>这段代码，如果编译器优化级别很低，比如vs的debug或g++的O0和O1，编译出来的执行序列是和语句一样的，但是在优化编译下会指令乱序（gcc）：&nbsp;</div><div>movl b, %eax&nbsp;</div><div>movl $0, b&nbsp;</div><div>addl $1, %eax&nbsp;</div><div>movl %eax, a&nbsp;</div><div>可以看到，将b加载到eax后，立即执行了b=0，然后才对eax+1，再复制给a，相当于把b=0提前执行了，假如我们在另一个线程判断b的值是否为0，然后访问a的值，就可能和预期不符，因为可能还没执行到写a的内存就访问了，出现了不一致的情况（vs2008下也乱序了）&nbsp;</div><div>P.S.这个乱序的原因，个人猜测是将对b的存取聚在一起，减少cpu cache miss&nbsp;</div><div></div><div>这里，先给出acquire和release的语义：&nbsp;</div><div>acquire：禁止read-acquire之后所有的内存读写操作，被提前到read-acquire操作之前进行&nbsp;</div><div>release：禁止write-release之前所有的内存读写操作，被推迟到write-release操作之后进行&nbsp;</div><div>具体到volatile变量，就是说，对于一个volatile变量的读操作，所有代码中在它后面的指令不得提前到它之前执行，反之对于写操作，所有在它之前的代码不得延迟到它之后执行&nbsp;</div><div></div><div>很明显上面的例子中，我们需要release语义，因此可以把b修饰为volatile，根据release语义，b=3之前的所有语句不得乱序到它后面执行，在vs下测试时，的确起作用了：&nbsp;</div><div>00401000 mov eax,dword ptr [___defaultmatherr+8 (40301Ch)] //load b&nbsp;</div><div>00401005 inc eax //+1&nbsp;</div><div>00401006 mov dword ptr [__fmode+4 (403378h)],eax //store a&nbsp;</div><div>0040100B mov dword ptr [___defaultmatherr+8 (40301Ch)],0 //store b&nbsp;</div><div></div><div>但是，如果在gcc下测试，给b加volatile是没有任何效果的，对a的赋值依然像上面一样被乱序到后面执行，这显然是有问题的。不过这并不是gcc的bug，而是因为C语言对于volatile并没有规定acquire和release语义，gcc就没有实现，那为啥vs可以呢，因为vs实现了这俩语义（windows程序员欢呼吧）&nbsp;</div><div></div><div>如果要解决上面这个例子在gcc的问题，只需要把a也声明为volatile即可，也就是说，虽然gcc没有实现对单独volatile变量操作时release语义，但是多个volatile变量的顺序似乎是保证的。说似乎，因为我还没有找到权威资料证明，但从经验来看是没什么问题。对于实际问题，比如实现一个无锁环形队列，最好还是用-S参数输出下汇编，确认下没有乱序比较好&nbsp;</div><div></div><div>如果说，我们已经注意到并避免了上述问题，甚至对可执行文件反汇编，并对汇编做了确认，那是不是就没问题了？可惜这个想法还是太天真了，即便你保证了汇编（机器代码）级别的有序性，它最终还是要到cpu里面执行的，而cpu为了执行速度，也会采用乱序优化，这个是volatile无法控制的领域&nbsp;</div><div></div><div>回忆一下大学的计算机组成和体系结构，cpu是由一些硬件组件组成，而硬件组件的工作是可以高度并行的，于是有了最经典的五级流水线，而现在的cpu的流水线是非常复杂的，还有指令乱序和分支预测等技术&nbsp;</div><div></div><div>cpu指令乱序是一种统筹规划，比如小时候都做过统筹相关的题目：小明要做若干菜，其中对每个菜，切菜xx分钟，煮xx分钟，腌xx分钟，其中若干步骤可以并行，问如何安排等&nbsp;</div><div></div><div>举个简单的例子（寄存器表达式伪代码）：&nbsp;</div><div>eax /= ebx&nbsp;</div><div>eax += eax&nbsp;</div><div>ecx -= edx&nbsp;</div><div>ecx *= ecx&nbsp;</div><div>执行这个序列的时候，如果按最原始的办法，一个个指令串行执行，则可能很浪费，因为第一个除法可能要消耗十几个cpu周期，后面的加减乘法又有几个周期。如果不用乱序执行，只考虑流水线，则效率提升也不大，因为只有第二和第三条指令能在流水线同时执行，第二条指令依赖第一条的结果，第四条依赖第三条的结果，流水线会停顿&nbsp;</div><div></div><div>如果有了乱序执行技术，则cpu在执行第一条指令时会对后面的若干指令进行分析，找到可以提前执行的指令，具体在上面的例子，由于第二条要等第一条的eax结果，因此加法就停顿住，但是第三条和第一条没有关系，就提前到cpu执行，由于减法很快，第三条执行完后还可以把第四条提前执行，甚至可能三四都执行完成后，第一条还在除法器里面循环，然后一直等到第一条执行完，这时候再执行第二条的加法，最后的结果和简单串行执行一样，但是总耗时只是一次除法和一次加法而已&nbsp;</div><div></div><div>上面是用寄存器运算乱序做个例子，对于volatile变量来说，主要受内存访问指令乱序的影响，具体的就是load和store两条指令顺序的问题，例如：&nbsp;</div><div>x=x*x&nbsp;</div><div>y=1&nbsp;</div><div>假设我们用y=1来表示x计算完毕，根据上面的讨论，x和y都应该是volatile，编译后可能是（寄存器表达式伪代码）：&nbsp;</div><div>eax = x //load&nbsp;</div><div>eax *= eax&nbsp;</div><div>x = eax //store&nbsp;</div><div>y = 1 //store&nbsp;</div><div>cpu在执行这段时，分析出前三条指令有依赖关系，第四条跟前三条无关，于是可能在算乘法的时候将y的store指令乱序执行，如果另一个线程在另一个核执行，检测到y的值已经被store，以为x算完了，可能出问题，因为这时候可能还在算乘法，没有store x，这个例子中是将两次无关（cpu认为无关，但实际上是有关）的store乱序执行，简称SS乱序，对应的还有LL乱序，LS乱序和SL乱序&nbsp;</div><div></div><div>很显然SS乱序会对我们上面讨论的volatile变量或无锁队列产生影响，可以从acquire和release语义来看这个问题：&nbsp;</div><div>acquire：一个volatile变量的读行为之后的所有动作不应该提前到读之前，因此LL和LS乱序是不可以的&nbsp;</div><div>release：一个volatile变量的写行为之前的所有动作不应该推迟到写之后，因此LS和SS乱序是不可以的&nbsp;</div><div></div><div>于是乎，也只剩下SL乱序在这种情况下是安全的，幸运的是，我们常用的x86和amd64架构的cpu都只支持SL乱序，所以只要正确使用volatile和实现代码，基本不会出什么问题，各种CPU的乱序支持参考下图（前四行）：&nbsp;</div><div></div><div></div><div>可以看到，只有四种cpu只支持SL乱序，而其他cpu为了提高运行效率，支持力度会高一些，大部分四种乱序都支持&nbsp;</div><div></div><div>既然乱序执行是cpu本身的特性，那么在支持各种乱序的cpu上，单纯依靠软件岂不是无法实现并发访问了？这显然是不可能的，解铃还须系铃人，可以利用一些特殊的机器指令能实现acquire和release语义，这也是操作系统中各种互斥量实现的基础</div><div>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</div><div>版权声明：本文为CSDN博主「xtlisk」的原创文章，遵循 CC 4.0 BY-SA 版权协议，转载请附上原文出处链接及本声明。</div><div>原文链接：https://blog.csdn.net/xtlisk/article/details/39098981</div><img src ="http://www.cppblog.com/markqian86/aggbug/217248.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-21 16:13 <a href="http://www.cppblog.com/markqian86/archive/2020/04/21/217248.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>浅谈c语言代码段 数据段 bss段</title><link>http://www.cppblog.com/markqian86/archive/2020/04/20/217244.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Mon, 20 Apr 2020 07:37:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/20/217244.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217244.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/20/217244.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217244.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217244.html</trackback:ping><description><![CDATA[<p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;"><span style="color: #ff0000;">代码段、数据段、bss段</span></p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">(1)编译器在编译程序的时候，将程序中的所有的元素分成了一些组成部分，各部分构成一个段，所以说段是可执行程序的组成部分。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">(2)<span style="color: #ff0000;">代码段</span>：代码段就是程序中的可执行部分，直观理解代码段就是函数堆叠组成的。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">(3)<span style="color: #ff0000;">数据段</span>（也被称为数据区、静态数据区、静态区）：数据段就是程序中的数据，直观理解就是C语言程序中的<span style="color: #ff0000;">全局变量</span>。（注意：全局变量才算是程序的数据，局部变量不算程序的数据，只能算是函数的数据）</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">(4)<span style="color: #ff0000;">bss段</span>（又叫ZI(zero initial)段）：bss段的特点就是被初始化为0，bss段本质上也是属于数据段，bss段就是被初始化为0的数据段。&nbsp;<span style="color: #ff0000;">注意区分</span>：数据段（.data）和bss段的区别和联系：二者本来没有本质区别，都是用来存放C程序中的全局变量的。区别在于把显示初始化为非零的全局变量存在.data段中，而把显式初始化为0或者并未显式初始化（C语言规定未显式初始化的全局变量值默认为0）的全局变量存在bss段。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;"><span style="color: #ff0000;">有些特殊数据会被放到代码段</span></p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">(1)C语言中使用char *p = "linux";定义字符串时，字符串"linux"实际被分配在代码段，也就是说这个"linux"字符串实际上是一个常量字符串而不是变量字符串。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">(2)const型常量：C语言中const关键字用来定义常量，常量就是不能被改变的量。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;"><span style="color: #ff0000;">const的实现方法至少有2种</span>：</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">第一种就是编译将const修饰的变量放在代码段去以实现不能修改（普遍见于各种单片机的编译器）；</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">第二种就是由编译器来检查以确保const型的常量不会被修改，实际上const型的常量还是和普通变量一样放在数据段的（gcc中就是这样实现的）。</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">显式初始化为非零的全局变量和静态局部变量放在数据段</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">(1)放在.<span style="color: red;">data段</span>的变量有2种：第一种是显式初始化为<span style="color: red;">非零的全局变量</span>。第二种是<span style="color: red;">静态局部变量</span>，也就是static修饰的局部变量。（普通局部变量分配在栈上，静态局部变量分配在.data段）</p><p style="margin-top: 10px; margin-bottom: 10px; font-family: Verdana, Arial, Helvetica, sans-serif; background-color: #ffffff;">未初始化或显式初始化为0的全局变量放在bss段 (1)bss段和.data段并没有本质区别，几乎可以不用明确去区分这两种。<br /><br /><br /><span style="color: #4d4d4d; font-family: &quot;Microsoft YaHei&quot;, &quot;SF Pro Display&quot;, Roboto, Noto, Arial, &quot;PingFang SC&quot;, sans-serif; font-size: 16px;">C++中</span><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; word-wrap: break-word; font-family: &quot;Microsoft YaHei&quot;, &quot;SF Pro Display&quot;, Roboto, Noto, Arial, &quot;PingFang SC&quot;, sans-serif; font-size: 16px; color: #f33b45;"><span style="outline: 0px; margin: 0px; padding: 0px; box-sizing: border-box; font-weight: 700; word-wrap: break-word;">虚函数表位于只读数据段（.rodata），也就是C++内存模型中的常量区；而虚函数则位于代码段（.text），也就是C++内存模型中的代码区。</span></span></p><img src ="http://www.cppblog.com/markqian86/aggbug/217244.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-20 15:37 <a href="http://www.cppblog.com/markqian86/archive/2020/04/20/217244.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>malloc、calloc、realloc的区别</title><link>http://www.cppblog.com/markqian86/archive/2020/04/20/217243.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Mon, 20 Apr 2020 07:29:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/04/20/217243.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217243.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/04/20/217243.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217243.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217243.html</trackback:ping><description><![CDATA[<div>(1)C语言跟内存分配方式</div><div></div><div>&lt;1&gt;从静态存储区域分配.</div><div>&nbsp; &nbsp; &nbsp; &nbsp;内存在程序编译的时候就已经分配好，这块内存在程序的整个运行期间都存在.例如全局变量、static变量.</div><div>&lt;2&gt;在栈上创建</div><div>&nbsp; &nbsp; &nbsp; &nbsp;在执行函数时，函数内局部变量的存储单元都可以在栈上创建，函数执行结束时这些存储单元自动被释放.栈内存分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限.</div><div></div><div>&lt;3&gt;从堆上分配，亦称动态内存分配.</div><div>&nbsp; &nbsp; &nbsp; &nbsp;程序在运行的时候用malloc或new申请任意多少的内存，程序员自己负责在何时用free或delete释放内存.动态内存的生存期由用户决定，使用非常灵活，但问题也最多.</div><div></div><div></div><div>(2)C语言跟内存申请相关的函数主要有 alloca、calloc、malloc、free、realloc等.</div><div>&nbsp; &nbsp; &lt;1&gt;alloca是向栈申请内存,因此无需释放.</div><div>&nbsp; &nbsp; &lt;2&gt;malloc分配的内存是位于堆中的,并且没有初始化内存的内容,因此基本上malloc之后,调用函数memset来初始化这部分的内存空间.</div><div>&nbsp; &nbsp; &lt;3&gt;calloc则将初始化这部分的内存,设置为0.</div><div>&nbsp; &nbsp; &lt;4&gt;realloc则对malloc申请的内存进行大小的调整.</div><div>&nbsp; &nbsp; &lt;5&gt;申请的内存最终需要通过函数free来释放.</div><div>&nbsp; &nbsp; 当程序运行过程中malloc了,但是没有free的话,会造成内存泄漏.一部分的内存没有被使用,但是由于没有free,因此系统认为这部分内存还在使用,造成不断的向系统申请内存,使得系统可用内存不断减少.但是内存泄漏仅仅指程序在运行时,程序退出时,OS将回收所有的资源.因此,适当的重起一下程序,有时候还是有点作用.</div><div>【attention】</div><div>&nbsp; &nbsp; 三个函数的申明分别是:</div><div>&nbsp; &nbsp; &nbsp; &nbsp; void* malloc(unsigned size);</div><div>&nbsp; &nbsp; &nbsp; &nbsp; void* realloc(void* ptr, unsigned newsize);&nbsp;&nbsp;</div><div>&nbsp; &nbsp; &nbsp; &nbsp; void* calloc(size_t numElements, size_t sizeOfElement);&nbsp;</div><div>&nbsp; &nbsp; 都在stdlib.h函数库内，它们的返回值都是请求系统分配的地址,如果请求失败就返回NULL.</div><div>&nbsp; &nbsp; (1)函数malloc()</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 在内存的动态存储区中分配一块长度为size字节的连续区域，参数size为需要内存空间的长度，返回该区域的首地址.</div><div>&nbsp; &nbsp; (2)函数calloc()</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 与malloc相似,参数sizeOfElement为申请地址的单位元素长度,numElements为元素个数，即在内存中申请numElements*sizeOfElement字节大小的连续地址空间.</div><div>&nbsp; &nbsp; (3)函数realloc()</div><div>&nbsp; &nbsp; &nbsp; &nbsp; 给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址长度.</div><div>&nbsp; &nbsp; 区别:</div><div>&nbsp; &nbsp; (1)函数malloc不能初始化所分配的内存空间,而函数calloc能.如果由malloc()函数分配的内存空间原来没有被使用过，则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据.也就是说，使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题.</div><div>&nbsp; &nbsp; (2)函数calloc() 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零.</div><div>&nbsp; &nbsp; (3)函数malloc向系统申请分配指定size个字节的内存空间.返回类型是 void*类型.void*表示未确定类型的指针.C,C++规定，void* 类型可以强制转换为任何其它类型的指针.</div><div>&nbsp; &nbsp; (4)realloc可以对给定的指针所指的空间进行扩大或者缩小，无论是扩张或是缩小，原有内存的中内容将保持不变.当然，对于缩小，则被缩小的那一部分的内容会丢失.realloc并不保证调整后的内存空间和原来的内存空间保持同一内存地址.相反，realloc返回的指针很可能指向一个新的地址.</div><div>&nbsp; &nbsp; (5)realloc是从堆上分配内存的.当扩大一块内存空间时，realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节，如果能够满足，自然天下太平；如果数据后面的字节不够，问题就出来了，那么就使用堆上第一个有足够大小的自由块，现存的数据然后就被拷贝至新的位置，而老块则放回到堆上.这句话传递的一个重要的信息就是数据可能被移动.&nbsp;</div><div>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</div><div>版权声明：本文为CSDN博主「shuaishuai80」的原创文章，遵循 CC 4.0 BY-SA 版权协议，转载请附上原文出处链接及本声明。</div><div>原文链接：https://blog.csdn.net/shuaishuai80/article/details/6140979</div><img src ="http://www.cppblog.com/markqian86/aggbug/217243.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-04-20 15:29 <a href="http://www.cppblog.com/markqian86/archive/2020/04/20/217243.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows性能监视器-性能分析</title><link>http://www.cppblog.com/markqian86/archive/2020/03/12/217197.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 12 Mar 2020 03:19:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/03/12/217197.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217197.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/03/12/217197.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217197.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217197.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;3。磁盘I/O分析方法&nbsp;&nbsp;&nbsp;&nbsp; (1)计算每磁盘的I/O数每磁盘的I/O数可用来与磁盘的I/O能力进行对比，如果经过计算得到的每磁盘I/O数超过了磁盘标称的I/O能力，则说明确实存在磁盘的性能瓶颈。&nbsp;&nbsp;&nbsp; &nbsp;(2)与Processor: Privileged Time&nbsp;合并进行分析&n...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2020/03/12/217197.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/217197.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-03-12 11:19 <a href="http://www.cppblog.com/markqian86/archive/2020/03/12/217197.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux 通过mount挂载windows共享文件</title><link>http://www.cppblog.com/markqian86/archive/2020/03/09/217190.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Mon, 09 Mar 2020 07:26:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/03/09/217190.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217190.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/03/09/217190.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217190.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217190.html</trackback:ping><description><![CDATA[<div style="color: #333333; font-family: &quot;Microsoft Yahei&quot;, 微软雅黑, arial, 宋体, sans-serif; font-size: 16px; text-align: justify; background-color: #ffffff;"><p style="margin: 0px; padding: 0px;"># mount -t cifs //10.0.0.1/share /mnt/sharefolder -o username=sensirx,password=sensirx,vers=2.0</p></div><div style="position: relative; height: 13px; margin-top: 5px; color: #333333; font-family: &quot;Microsoft Yahei&quot;, 微软雅黑, arial, 宋体, sans-serif; font-size: 16px; text-align: justify; background-color: #ffffff;"></div><img src ="http://www.cppblog.com/markqian86/aggbug/217190.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-03-09 15:26 <a href="http://www.cppblog.com/markqian86/archive/2020/03/09/217190.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>高性能无锁队列Disruptor</title><link>http://www.cppblog.com/markqian86/archive/2020/01/16/217089.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Thu, 16 Jan 2020 12:12:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/01/16/217089.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217089.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/01/16/217089.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217089.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217089.html</trackback:ping><description><![CDATA[<div><div><h1>1.何为队列</h1> <p>听到队列相信大家对其并不陌生，在我们现实生活中队列随处可见，去超市结账，你会看见大家都会一排排的站得好好的，等待结账，为什么要站得一排排的，你想象一下大家都没有素质，一窝蜂的上去结账，不仅让这个超市崩溃，还会容易造成各种踩踏事件，当然这些事其实在我们现实中也是会经常发生。</p> <p>当然在计算机世界中，队列是属于一种数据结构，队列采用的FIFO(first in firstout)，新元素（等待进入队列的元素）总是被插入到尾部，而读取的时候总是从头部开始读取。在计算中队列一般用来做排队(如线程池的等待排队，锁的等待排队)，用来做解耦（生产者消费者模式），异步等等。</p> <h1>2.jdk中的队列</h1> <p>在jdk中的队列都实现了java.util.Queue接口，在队列中又分为两类，一类是线程不安全的，ArrayDeque，LinkedList等等，还有一类都在java.util.concurrent包下属于线程安全，而在我们真实的环境中，我们的机器都是属于多线程，当多线程对同一个队列进行排队操作的时候，如果使用线程不安全会出现，覆盖数据，数据丢失等无法预测的事情，所以我们这个时候只能选择线程安全的队列。在jdk中提供的线程安全的队列下面简单列举部分队列:</p> <table> <thead> <tr> <th>队列名字</th> <th>是否加锁</th> <th>数据结构</th> <th>关键技术点</th> <th>是否有锁</th> <th>是否有界</th> </tr> </thead> <tbody> <tr> <td>ArrayBlockingQueue</td> <td>是</td> <td>数组array</td> <td>ReentrantLock</td> <td>有锁</td> <td>有界</td> </tr> <tr> <td>LinkedBlockingQueue</td> <td>是</td> <td>链表</td> <td>ReentrantLock</td> <td>有锁</td> <td>有界</td> </tr> <tr> <td>LinkedTransferQueue</td> <td>否</td> <td>链表</td> <td>CAS</td> <td>无锁</td> <td>无界</td> </tr> <tr> <td>ConcurrentLinkedQueue</td> <td>否</td> <td>链表</td> <td>CAS</td> <td>无锁</td> <td>无界</td> </tr> </tbody> </table> <p>我们可以看见，我们无锁的队列是无界的，有锁的队列是有界的，这里就会涉及到一个问题，我们在真正的线上环境中，无界的队列，对我们系统的影响比较大，有可能会导致我们内存直接溢出，所以我们首先得排除无界队列，当然并不是无界队列就没用了，只是在某些场景下得排除。其次还剩下ArrayBlockingQueue，LinkedBlockingQueue两个队列，他们两个都是用ReentrantLock控制的线程安全，他们两个的区别一个是数组，一个是链表，在队列中，一般获取这个队列元素之后紧接着会获取下一个元素，或者一次获取多个队列元素都有可能，而数组在内存中地址是连续的，在操作系统中会有缓存的优化(下面也会介绍缓存行)，所以访问的速度会略胜一筹，我们也会尽量去选择ArrayBlockingQueue。而事实证明在很多第三方的框架中，比如早期的log4j异步，都是选择的ArrayBlockingQueue。</p> <p>当然ArrayBlockingQueue，也有自己的弊端，就是性能比较低，为什么jdk会增加一些无锁的队列，其实就是为了增加性能，很苦恼，又需要无锁，又需要有界，这个时候恐怕会忍不住说一句你咋不上天呢？但是还真有人上天了。</p> <h1>3.Disruptor</h1> <p>Disruptor就是上面说的那个天，Disruptor是英国外汇交易公司LMAX开发的一个高性能队列，并且是一个开源的并发框架，并获得2011Duke&#8217;s程序框架创新奖。能够在无锁的情况下实现网络的Queue并发操作，基于Disruptor开发的系统单线程能支撑每秒600万订单。目前，包括Apache Storm、Camel、Log4j2等等知名的框架都在内部集成了Disruptor用来替代jdk的队列，以此来获得高性能。</p> <h2>3.1为什么这么牛逼？</h2> <p>上面已经把Disruptor吹出了花了，你肯定会产生疑问，他真的能有这么牛逼吗，我的回答是当然的，在Disruptor中有三大杀器:</p> <ul> <li>CAS</li> <li>消除伪共享</li> <li>RingBuffer 有了这三大杀器，Disruptor才变得如此牛逼。</li> </ul> <h3>3.1.1锁和CAS</h3> <p>我们ArrayBlockingQueue为什么会被抛弃的一点，就是因为用了重量级lock锁，在我们加锁过程中我们会把锁挂起，解锁后，又会把线程恢复,这一过程会有一定的开销，并且我们一旦没有获取锁，这个线程就只能一直等待，这个线程什么事也不能做。</p> <p>CAS（compare and swap），顾名思义先比较在交换，一般是比较是否是老的值，如果是的进行交换设置，大家熟悉乐观锁的人都知道CAS可以用来实现乐观锁，CAS中没有线程的上下文切换，减少了不必要的开销。 这里使用JMH，用两个线程，每次1一次调用，在我本机上进行测试，代码如下:</p> <pre><code bash=""  copyable"="">@BenchmarkMode({Mode.SampleTime}) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Warmup(iterations=3, time = 5, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations=1,batchSize = 100000000) @Threads(2) @Fork(1) @State(Scope.Benchmark) public class Myclass {     Lock lock = new ReentrantLock();     long i = 0;     AtomicLong atomicLong = new AtomicLong(0);     @Benchmark     public void measureLock() {         lock.lock();         i++;         lock.unlock();     }     @Benchmark     public void measureCAS() {         atomicLong.incrementAndGet();     }     @Benchmark     public void measureNoLock() {         i++;     } } 复制代码</code></pre><p>测试出来结果如下:</p> <table> <thead> <tr> <th>测试项目</th> <th>测试结果</th> </tr> </thead> <tbody> <tr> <td>Lock</td> <td>26000ms</td> </tr> <tr> <td>CAS</td> <td>4840ms</td> </tr> <tr> <td>无锁</td> <td>197ms</td> </tr> </tbody> </table> <p>可以看见Lock是五位数，CAS是四位数，无锁更小是三位数。 由此我们可以知道Lock&gt;CAS&gt;无锁。</p> <p>而我们的Disruptor中使用的就是CAS，他利用CAS进行队列中的一些下标设置，减少了锁的冲突，提高了性能。</p> <p>另外对于jdk中其他的无锁队列也是使用CAS，原子类也是使用CAS。</p> <h3>3.1.2伪共享</h3> <p>谈到了伪共享就不得不说计算机CPU缓存,缓存大小是CPU的重要指标之一，而且缓存的结构和大小对CPU速度的影响非常大，CPU内缓存的运行频率极高，一般是和处理器同频运作，工作效率远远大于系统内存和硬盘。实际工作时，CPU往往需要重复读取同样的数据块，而缓存容量的增大，可以大幅度提升CPU内部读取数据的命中率，而不用再到内存或者硬盘上寻找，以此提高系统性能。但是从CPU芯片面积和成本的因素来考虑，缓存都很小。</p> <p>&nbsp;</p><figure><img inited"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164e946ff882653a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164e946ff882653a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="387" data-height="352"  alt="" /><figcaption></figcaption></figure><p>&nbsp;</p> <p>CPU缓存可以分为一级缓存，二级缓存，如今主流CPU还有三级缓存，甚至有些CPU还有四级缓存。每一级缓存中所储存的全部数据都是下一级缓存的一部分，这三种缓存的技术难度和制造成本是相对递减的，所以其容量也是相对递增的。</p> <p>为什么CPU会有L1、L2、L3这样的缓存设计？主要是因为现在的处理器太快了，而从内存中读取数据实在太慢（一个是因为内存本身速度不够，另一个是因为它离CPU太远了，总的来说需要让CPU等待几十甚至几百个时钟周期），这个时候为了保证CPU的速度，就需要延迟更小速度更快的内存提供帮助，而这就是缓存。对这个感兴趣可以把电脑CPU拆下来，自己把玩一下。</p> <p>每一次你听见intel发布新的cpu什么,比如i7-7700k,8700k，都会对cpu缓存大小进行优化，感兴趣可以自行下来搜索，这些的发布会或者发布文章。</p> <p>Martin和Mike的 QConpresentation演讲中给出了一些每个缓存时间：</p> <table> <thead> <tr> <th>从CPU到</th> <th>大约需要的CPU周期</th> <th>大约需要的时间</th> </tr> </thead> <tbody> <tr> <td>主存</td> <td></td> <td>约60-80纳秒</td> </tr> <tr> <td>QPI 总线传输(between sockets, not drawn)</td> <td></td> <td>约20ns</td> </tr> <tr> <td>L3 cache</td> <td>约40-45 cycles</td> <td>约15ns</td> </tr> <tr> <td>L2 cache</td> <td>约10 cycles</td> <td>约3ns</td> </tr> <tr> <td>L1 cache</td> <td>约3-4 cycles</td> <td>约1ns</td> </tr> <tr> <td>寄存器</td> <td></td> <td>1 cycle</td> </tr> </tbody> </table> <h4>缓存行</h4> <p>在cpu的多级缓存中，并不是以独立的项来保存的，而是类似一种pageCahe的一种策略，以缓存行来保存，而缓存行的大小通常是64字节，在Java中Long是8个字节，所以可以存储8个Long,举个例子，你访问一个long的变量的时候，他会把帮助再加载7个，我们上面说为什么选择数组不选择链表，也就是这个原因，在数组中可以依靠缓冲行得到很快的访问。 </p><figure><img inited=""  loaded"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164e99de34def2a1?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164e99de34def2a1?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="1144" data-height="673"  alt="" /><figcaption></figcaption></figure><p>&nbsp;</p> <p>缓存行是万能的吗？NO，因为他依然带来了一个缺点，我在这里举个例子说明这个缺点，可以想象有个数组队列，ArrayQueue，他的数据结构如下:</p> <pre><code bash=""  copyable"="">class ArrayQueue{     long maxSize;     long currentIndex; } 复制代码</code></pre><p>对于maxSize是我们一开始就定义好的，数组的大小，对于currentIndex，是标志我们当前队列的位置，这个变化比较快，可以想象你访问maxSize的时候，是不是把currentIndex也加载进来了，这个时候，其他线程更新currentIndex,就会把cpu中的缓存行置位无效，请注意这是CPU规定的，他并不是只吧currentIndex置位无效，如果此时又继续访问maxSize他依然得继续从内存中读取，但是MaxSize却是我们一开始定义好的，我们应该访问缓存即可，但是却被我们经常改变的currentIndex所影响。</p> <p>&nbsp;</p><figure><img inited=""  loaded"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164e9b05c790e03a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164e9b05c790e03a?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="1173" data-height="726"  alt="" /><figcaption></figcaption></figure><p>&nbsp;</p> <h4>Padding的魔法</h4> <p>为了解决上面缓存行出现的问题，在Disruptor中采用了Padding的方式，</p> <pre><code bash=""  copyable"="">class LhsPadding {     protected long p1, p2, p3, p4, p5, p6, p7; }  class Value extends LhsPadding {     protected volatile long value; }  class RhsPadding extends Value {     protected long p9, p10, p11, p12, p13, p14, p15; } 复制代码</code></pre><p>其中的Value就被其他一些无用的long变量给填充了。这样你修改Value的时候，就不会影响到其他变量的缓存行。</p> <p>最后顺便一提，在jdk8中提供了@Contended的注解，当然一般来说只允许Jdk中内部，如果你自己使用那就得配置Jvm参数 -RestricContentended = fase，将限制这个注解置位取消。很多文章分析了ConcurrentHashMap，但是都把这个注解给忽略掉了，在ConcurrentHashMap中就使用了这个注解，在ConcurrentHashMap每个桶都是单独的用计数器去做计算，而这个计数器由于时刻都在变化，所以被用这个注解进行填充缓存行优化，以此来增加性能。</p> <p>&nbsp;</p><figure><img inited=""  loaded"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164e9e48c68d3a2e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164e9e48c68d3a2e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="1280" data-height="720"  alt="" /><figcaption></figcaption></figure><p>&nbsp;</p> <h3>3.1.3RingBuffer</h3> <p>在Disruptor中采用了数组的方式保存了我们的数据，上面我们也介绍了采用数组保存我们访问时很好的利用缓存，但是在Disruptor中进一步选择采用了环形数组进行保存数据，也就是RingBuffer。在这里先说明一下环形数组并不是真正的环形数组，在RingBuffer中是采用取余的方式进行访问的，比如数组大小为 10，0访问的是数组下标为0这个位置，其实10，20等访问的也是数组的下标为0的这个位置。</p> <blockquote> <p>实际上，在这些框架中取余并不是使用%运算，都是使用的&amp;与运算，这就要求你设置的大小一般是2的N次方也就是，10,100,1000等等，这样减去1的话就是，1，11，111，就能很好的使用index &amp; (size -1),这样利用位运算就增加了访问速度。 如果在Disruptor中你不用2的N次方进行大小设置，他会抛出buffersize必须为2的N次方异常。</p> </blockquote> <p>当然其不仅解决了数组快速访问的问题，也解决了不需要再次分配内存的问题，减少了垃圾回收，因为我们0，10，20等都是执行的同一片内存区域，这样就不需要再次分配内存，频繁的被JVM垃圾回收器回收。</p> <p>&nbsp;</p><figure><img inited=""  loaded"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164ea1033b955f2e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164ea1033b955f2e?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="512" data-height="456"  alt="" /><figcaption></figcaption></figure><p>&nbsp;</p> <p>自此三大杀器已经说完了，有了这三大杀器为Disruptor如此高性能垫定了基础。接下来还会在讲解如何使用Disruptor和Disruptor的具体的工作原理。</p> <h2>3.2Disruptor怎么使用</h2> <p>下面举了一个简单的例子:</p> <pre><code bash=""  copyable"="">ublic static void main(String[] args) throws Exception {         // 队列中的元素         class Element {             @Contended             private String value;               public String getValue() {                 return value;             }              public void setValue(String value) {                 this.value = value;             }         }          // 生产者的线程工厂         ThreadFactory threadFactory = new ThreadFactory() {             int i = 0;             @Override             public Thread newThread(Runnable r) {                 return new Thread(r, "simpleThread" + String.valueOf(i++));             }         };          // RingBuffer生产工厂,初始化RingBuffer的时候使用         EventFactory&lt;Element&gt; factory = new EventFactory&lt;Element&gt;() {             @Override             public Element newInstance() {                 return new Element();             }         };          // 处理Event的handler         EventHandler&lt;Element&gt; handler = new EventHandler&lt;Element&gt;() {             @Override             public void onEvent(Element element, long sequence, boolean endOfBatch) throws InterruptedException {                 System.out.println("Element: " + Thread.currentThread().getName() + ": " + element.getValue() + ": " + sequence); //                Thread.sleep(10000000);             }         };           // 阻塞策略         BlockingWaitStrategy strategy = new BlockingWaitStrategy();          // 指定RingBuffer的大小         int bufferSize = 8;          // 创建disruptor，采用单生产者模式         Disruptor&lt;Element&gt; disruptor = new Disruptor(factory, bufferSize, threadFactory, ProducerType.SINGLE, strategy);          // 设置EventHandler         disruptor.handleEventsWith(handler);          // 启动disruptor的线程         disruptor.start();         for (int i = 0; i &lt; 10; i++) {             disruptor.publishEvent((element, sequence) -&gt; {                 System.out.println("之前的数据" + element.getValue() + "当前的sequence" + sequence);                 element.setValue("我是第" + sequence + "个");             });          }     } 复制代码</code></pre><p>在Disruptor中有几个比较关键的: ThreadFactory：这是一个线程工厂，用于我们Disruptor中生产者消费的时候需要的线程。 EventFactory：事件工厂，用于产生我们队列元素的工厂，在Disruptor中，他会在初始化的时候直接填充满RingBuffer，一次到位。 EventHandler：用于处理Event的handler，这里一个EventHandler可以看做是一个消费者，但是多个EventHandler他们都是独立消费的队列。 WorkHandler:也是用于处理Event的handler，和上面区别在于，多个消费者都是共享同一个队列。 WaitStrategy：等待策略，在Disruptor中有多种策略，来决定消费者获消费时，如果没有数据采取的策略是什么？下面简单列举一下Disruptor中的部分策略</p> <ul> <li> <p>BlockingWaitStrategy：通过线程阻塞的方式，等待生产者唤醒，被唤醒后，再循环检查依赖的sequence是否已经消费。</p> </li> <li> <p>BusySpinWaitStrategy：线程一直自旋等待，可能比较耗cpu</p> </li> <li> <p>LiteBlockingWaitStrategy：线程阻塞等待生产者唤醒，与BlockingWaitStrategy相比，区别在signalNeeded.getAndSet,如果两个线程同时访问一个访问waitfor,一个访问signalAll时，可以减少lock加锁次数.</p> </li> <li> <p>LiteTimeoutBlockingWaitStrategy：与LiteBlockingWaitStrategy相比，设置了阻塞时间，超过时间后抛异常。</p> </li> <li> <p>YieldingWaitStrategy：尝试100次，然后Thread.yield()让出cpu</p> </li> </ul> <p>EventTranslator:实现这个接口可以将我们的其他数据结构转换为在Disruptor中流通的Event。</p> <h2>3.3工作原理</h2> <p>上面已经介绍了CAS，减少伪共享,RingBuffer三大杀器，介绍下来说一下Disruptor中生产者和消费者的整个流程。</p> <h3>3.3.1生产者</h3> <p>对于生产者来说，可以分为多生产者和单生产者，用ProducerType.Single,和ProducerType.MULTI区分，多生产者和单生产者来说多了CAS，因为单生产者由于是单线程，所以不需要保证线程安全。</p> <p>在disruptor中通常用disruptor.publishEvent和disruptor.publishEvents()进行单发和群发。</p> <p>在disruptor发布一个事件进入队列需要下面几个步骤:</p> <ol> <li>首先获取RingBuffer中下一个在RingBuffer上可以发布的位置，这个可以分为两类:</li> </ol> <ul> <li>从来没有写过的位置</li> <li>已经被所有消费者读过，可以在写的位置。 如果没有读取到会一直尝试去读，disruptor做的很巧妙，并没有一直占据CPU，而是通过LockSuport.park()，进行了一下将线程阻塞挂起操作，为的是不让CPU一直进行这种空循环，不然其他线程都抢不到CPU时间片。 <figure><img inited"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164ead78e814ab97?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164ead78e814ab97?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="1280" data-height="502"  alt="" /><figcaption></figcaption></figure> 获取位置之后会进行cas进行抢占，如果是单线程就不需要。</li> </ul> <ol start="2"> <li>接下来调用我们上面所介绍的EventTranslator将第一步中RingBuffer中那个位置的event交给EventTranslator进行重写。</li> <li>进行发布，在disruptor还有一个额外的数组用来记录当前ringBuffer所在位置目前最新的序列号是多少，拿上面那个0，10，20举例，写到10的时候这个avliableBuffer就在对应的位置上记录目前这个是属于10，有什么用呢后面会介绍。进行发布的时候需要更新这个avliableBuffer，然后进行唤醒所有阻塞的生产者。</li> </ol> <p>下面简单画一下流程，上面我们拿10举例是不对的，因为bufferSize必须要2的N次方，所以我们这里拿Buffersize=8来举例:下面介绍了当我们已经push了8个event也就是一圈的时候，接下来再push 3条消息的一些过程: 1.首先调用next(3)，我们当前在7这个位置上所以接下来三条是8，9，10，取余也就是0，1，2。 2.重写0，1，2这三个内存区域的数据。 3.写avaliableBuffer。</p> <p>&nbsp;</p><figure><img inited"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164eafcbb549c2a1?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164eafcbb549c2a1?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="1280" data-height="707"  alt="" /><figcaption></figcaption></figure><p>&nbsp;</p> <p>对了不知道大家对上述流程是不是很熟悉呢，对的那就是类似我们的2PC，两阶段提交，先进行RingBuffer的位置锁定，然后在进行提交和通知消费者。具体2PC的介绍可以参照我的另外一篇文章<a target="_blank" href="https://juejin.im/post/5b5a0bf9f265da0f6523913b" rel="">再有人问你分布式事务，给他看这篇文章</a>。</p> <h3>3.3.1消费者</h3> <p>对于消费者来说，上面介绍了分为两种，一种是多个消费者独立消费，一种是多个消费者消费同一个队列，这里介绍一下较为复杂的多个消费者消费同一个队列，能理解这个也就能理解独立消费。 在我们的disruptor.strat()方法中会启动我们的消费者线程以此来进行后台消费。在消费者中有两个队列需要我们关注，一个是所有消费者共享的进度队列，还有个是每个消费者独立消费进度队列。 1.对消费者共享队列进行下一个Next的CAS抢占，以及对自己消费进度的队列标记当前进度。 2.为自己申请可读的RingBuffer的Next位置，这里的申请不仅仅是申请到next，有可能会申请到比Next大的一个范围，阻塞策略的申请的过程如下:</p> <ul> <li>获取生产者对RingBuffer最新写的位置</li> <li>判断其是否小于我要申请读的位置</li> <li>如果大于则证明这个位置已经写了，返回给生产者。</li> <li>如果小于证明还没有写到这个位置，在阻塞策略中会进行阻塞，其会在生产者提交阶段进行唤醒。 3.对这个位置进行可读校验，因为你申请的位置可能是连续的，比如生产者目前在7，接下来申请读，如果消费者已经把8和10这个序列号的位置写进去了，但是9这个位置还没来得及写入，由于第一步会返回10，但是9其实是不能读的，所以得把位置向下收缩到8。 <figure><img inited"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164eb181982369f8?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164eb181982369f8?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="772" data-height="407"  alt="" /><figcaption></figcaption></figure> 4.如果收缩完了之后比当前next要小，则继续循环申请。 5.交给handler.onEvent()处理</li> </ul> <p>一样的我们举个例子，我们要申请next=8这个位置。 1.首先在共享队列抢占进度8，在独立队列写入进度7 2.获取8的可读的最大位置，这里根据不同的策略进行，我们选择阻塞，由于生产者生产了8，9，10，所以返回的是10，这样和后续就不需要再次和avaliableBuffer进行对比了。 3.最后交给handler进行处理。 </p><figure><img inited"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164eb4a5da8bdb92?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164eb4a5da8bdb92?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="1029" data-height="1280"  alt="" /><figcaption></figcaption></figure><p>&nbsp;</p> <h1>4.Log4j中的Disruptor</h1> <p>下面的图展现了Log4j使用Disruptor,ArrayBlockingQueue以及同步的Log4j吞吐量的对比，可以看见使用了Disruptor完爆了其他的，当然还有更多的框架使用了Disruptor，这里就不做介绍了。</p> <p>&nbsp;</p><figure><img inited=""  loaded"="" data-src="https://user-gold-cdn.xitu.io/2018/7/30/164eb4e0978f3ee0?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" src="https://user-gold-cdn.xitu.io/2018/7/30/164eb4e0978f3ee0?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" data-width="758" data-height="399"  alt="" /><figcaption></figcaption></figure><p>&nbsp;</p> <h1></h1></div><br />作者：咖啡拿铁<br />链接：https://juejin.im/post/5b5f10d65188251ad06b78e3<br />来源：掘金<br />著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。</div><img src ="http://www.cppblog.com/markqian86/aggbug/217089.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-01-16 20:12 <a href="http://www.cppblog.com/markqian86/archive/2020/01/16/217089.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>inline</title><link>http://www.cppblog.com/markqian86/archive/2020/01/13/217085.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Mon, 13 Jan 2020 03:28:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/01/13/217085.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217085.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/01/13/217085.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217085.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217085.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: inline是C++关键字，在函数声明或定义中，函数返回类型前加上关键字inline，即可以把函数指定为内联函数。这样可以解决一些频繁调用的函数大量消耗栈空间（栈内存）的问题。关键字inline必须与函数定义放在一起才能使函数成为内联函数，仅仅将inline放在函数声明前面不起任何作用。inline是一种&#8220;用于实现&#8221;的关键字，而不是一种&#8220;用于声明&#8221;的...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2020/01/13/217085.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/217085.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-01-13 11:28 <a href="http://www.cppblog.com/markqian86/archive/2020/01/13/217085.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>volatile </title><link>http://www.cppblog.com/markqian86/archive/2020/01/13/217084.html</link><dc:creator>长戟十三千</dc:creator><author>长戟十三千</author><pubDate>Mon, 13 Jan 2020 03:22:00 GMT</pubDate><guid>http://www.cppblog.com/markqian86/archive/2020/01/13/217084.html</guid><wfw:comment>http://www.cppblog.com/markqian86/comments/217084.html</wfw:comment><comments>http://www.cppblog.com/markqian86/archive/2020/01/13/217084.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/markqian86/comments/commentRss/217084.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/markqian86/services/trackbacks/217084.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 3）下面的函数被用来计算某个整数的平方，它能实现预期设计目标吗？如果不能，试回答存在什么问题：1234int&nbsp;square(volatile&nbsp;int&nbsp;*ptr){&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;((*ptr)&nbsp;*&nbsp;(*ptr));}3）这段代码是个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方，但是，...&nbsp;&nbsp;<a href='http://www.cppblog.com/markqian86/archive/2020/01/13/217084.html'>阅读全文</a><img src ="http://www.cppblog.com/markqian86/aggbug/217084.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/markqian86/" target="_blank">长戟十三千</a> 2020-01-13 11:22 <a href="http://www.cppblog.com/markqian86/archive/2020/01/13/217084.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>