<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>changan's blog</title><link>https://www.oneyearago.me/</link><description>Recent content on changan's blog</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Tue, 26 Mar 2024 18:20:39 +0800</lastBuildDate><atom:link href="https://www.oneyearago.me/index.xml" rel="self" type="application/rss+xml"/><item><title>音视频基础- 编解码 码控</title><link>https://www.oneyearago.me/2024/03/26/vedio_codec_01/</link><pubDate>Tue, 26 Mar 2024 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2024/03/26/vedio_codec_01/</guid><description>&lt;h3 id="x20-why-rate-control"&gt;why rate control&lt;/h3&gt;&#10;&lt;p&gt;Rate control is a crucial step in determining that tradeoff between size and quality.&lt;/p&gt;&#10;&lt;h3 id="Rate-Control-Modes"&gt;Rate Control Modes&lt;/h3&gt;&#10;&lt;h4 id="CQP-Constant-QP"&gt;CQP(Constant QP)&lt;/h4&gt;&#10;&lt;p&gt;恒定QP，所有编码图像区域，编码帧，编码片段都用相同的QP&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;画质不恒定&#10;&lt;ul&gt;&#10;&lt;li&gt;人类视觉主观特性，对不同纹理，运动快慢敏感度不同，相同QP对不同的区域编码，主观视觉体验不一致；&lt;/li&gt;&#10;&lt;li&gt;对平滑的画面，失真相对变化剧烈的画面失真要少，画质更好&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;码率不固定，内容相关&lt;/li&gt;&#10;&lt;li&gt;比特浪费&lt;/li&gt;&#10;&lt;li&gt;可用于算法验证或者测试，或者高质量的录制，如抖音的录制配置&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="CBR-constant-bitrate"&gt;CBR(constant bitrate):&lt;/h4&gt;&#10;&lt;p&gt;恒定码率，就是编码过程要求恒定码率；&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;不管是运动的片段还是比较静止的片段，都分配相同的码率，会导致因不同片段之间画质变化导致的主观体验较差；&lt;/li&gt;&#10;&lt;li&gt;在恒定码率较高的时候，在静止片段，画质/比特边际收益较低，造成不必要的比特浪费&lt;/li&gt;&#10;&lt;li&gt;使用场景，网络带宽要求较高，&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h4 id="VBR-Variable-bitrate"&gt;VBR(Variable bitrate):&lt;/h4&gt;&#10;&lt;p&gt;可变码率，编码过程根据实际编码帧、编码片段的内容特性，来分配合理的比特，在保证给定码率的前提下，尽可能提升画质主客观指标&lt;br&gt;&#10;常用的VBR 有crf, crf + VBV, abr, abr+vbv， abr + 2pass&lt;/p&gt;&#10;&lt;h4 id="CRF-Constant-rate-factor"&gt;CRF(Constant rate factor):&lt;/h4&gt;&#10;&lt;p&gt;基于恒定质量因子来对不同内容特性计算最佳的QP&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;人类主观特性&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;对平坦图像区域比较敏感，如天空，草原等，表征在编码器内部，Intra 模式的cost较小；&lt;/li&gt;&#10;&lt;li&gt;对细节比较丰富的区域不敏感，如小树叶等，表征在编码器内部，Intra 模式的cost 较大；&lt;/li&gt;&#10;&lt;li&gt;对运动缓慢的片段比较敏感，比如微风轻拂的花圃，表征在编码器内部，帧间的相似度较高，inter cost 较小；&lt;/li&gt;&#10;&lt;li&gt;对运动剧烈的片段比较不敏感，比如高铁窗外的电线杆，表征在编码器内部，帧间的相似度较小，inter cost 较大；&lt;br&gt;&#10;基于以上编码器在不同场景下的特性：&lt;br&gt;&#10;编码决策如下：&lt;/li&gt;&#10;&lt;li&gt;会对Cost 较大的区域或者视频片段，分配较小的码率，也就是是对应的QP较高；&lt;/li&gt;&#10;&lt;li&gt;对Cost 较小的区域，分配较多的码率，QP较小；&lt;/li&gt;&#10;&lt;li&gt;总体上编码视频片段在主观上相近&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="ABR-Average-Bitrate"&gt;ABR (Average Bitrate)&lt;/h4&gt;&#10;&lt;p&gt;平均码率，是VBR的一种，就是设定编码文件的平均码率，在编码过程中根据编码图像内容，分配不同的码率，在最终编码结束后，达到设定目标码率大小；&lt;br&gt;&#10;由于编码过程中，未编码的图像特性未知，所以编码器在编码过程中会基于已编码帧的统计，做出保守的决策，所以往往会对一些复杂的视频，做2pass 编码，基于第1 pass 编码统计特性，来做全局优化；&lt;br&gt;&#10;例子如下：&lt;/p&gt;</description></item><item><title>音视频基础- 直播业务</title><link>https://www.oneyearago.me/2024/03/15/vedio-basic-07/</link><pubDate>Fri, 15 Mar 2024 20:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2024/03/15/vedio-basic-07/</guid><description>&lt;h2 id="直播业务"&gt;直播业务&lt;/h2&gt;&#10;&lt;p&gt;直播的业务一般包括：推流，拉流/播放，回源，录制，时移，转码。&lt;/p&gt;&#10;&lt;h3 id="推流"&gt;推流&lt;/h3&gt;&#10;&lt;p&gt;直播推流是指通过推流协议，把音视频数据推送到CDN的服务器。&lt;/p&gt;&#10;&lt;p&gt;推流协议主要是RTMP，也可以使用WebRTC。&lt;/p&gt;&#10;&lt;p&gt;推流端需要把音视频数据封装成FLV格式或者RTP格式的数据。&lt;/p&gt;&#10;&lt;p&gt;推流端需要选择最近的一个节点推送数据。&lt;/p&gt;&#10;&lt;h3 id="拉流-播放"&gt;拉流/播放&lt;/h3&gt;&#10;&lt;p&gt;拉流：服务器之间读取实时流数据进行回源。&lt;/p&gt;&#10;&lt;p&gt;播放：客户端读取实时流数据进行播放。&lt;/p&gt;&#10;&lt;p&gt;拉流/播放可以使用服务器支持的不同的协议，服务器根据使用的协议返回相应的数据。&lt;/p&gt;&#10;&lt;p&gt;拉流/播放端需要选择最优的节点读取实时流数据。&lt;/p&gt;&#10;&lt;h3 id="回源"&gt;回源&lt;/h3&gt;&#10;&lt;p&gt;客户端向一个服务器请求某个流时，服务器有这个流就马上返回数据；服务器没有这个流，就需要去中心服务器请求这个流的数据，这就是回源。&lt;/p&gt;&#10;&lt;p&gt;回源可能是多级的，也可以是多协议支持的。&lt;/p&gt;&#10;&lt;h3 id="录制"&gt;录制&lt;/h3&gt;&#10;&lt;p&gt;在直播过程中，可以把直播产生的音视频数据保存下来，这个过程就是录制。&lt;/p&gt;&#10;&lt;p&gt;录制完成后，可以回放直播过程。&lt;/p&gt;&#10;&lt;p&gt;录制文件格式有hls,flv,以及mp4。&lt;/p&gt;&#10;&lt;h3 id="时移"&gt;时移&lt;/h3&gt;&#10;&lt;p&gt;在直播过程中，可能需要查看前面一段时间的数据，或者从前面几秒开始查看直播，这个就是时移做的功能。&lt;/p&gt;&#10;&lt;p&gt;时移是通过录制产生的切片信息实现的，所以时移使用是的HLS协议。&lt;/p&gt;&#10;&lt;p&gt;时移一般有两种播放方式：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;播放过去的一段时间&lt;/li&gt;&#10;&lt;li&gt;从过去的哪个时间点一直往后播放，直到结束&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="转码"&gt;转码&lt;/h3&gt;&#10;&lt;p&gt;在拉流/播放过程中，服务器遇到不支持的编码请求或者不同的分辨率，或者其他情况，就会触发转码。&lt;/p&gt;&#10;&lt;p&gt;转码就是通过程序，把不符合要求的流，转码成符合要求的流。&lt;/p&gt;&#10;&lt;p&gt;转码一般使用ffmpeg进行，同时也可以使用显卡进行硬件加速。&lt;/p&gt;&#10;&lt;h3 id="CDN分发"&gt;CDN分发&lt;/h3&gt;&#10;&lt;p&gt;主播推流，一般只推送到一个服务器，而播放却是在全国各地，这就需要CDN把流传输到各地的服务器，这就是CDN分发。&lt;/p&gt;&#10;&lt;p&gt;CDN分发一般是被动的，有访问需求才进行回源；也可以主动下发进行预热。&lt;/p&gt;&#10;&lt;h3 id="CDN加速"&gt;CDN加速&lt;/h3&gt;&#10;&lt;p&gt;客户端的网络环境差异很大，为了优化播放端的体验，CDN需要把播放引导到离客户最近的，网速最快的服务器，相当于CDN给客户端提升了速度，这就是CDN加速。&lt;/p&gt;&#10;&lt;p&gt;CDN加速主要是通过减少访问服务器的距离，提供大带宽的服务，选择相同的运营商，选择延时低的流媒体协议等手段来实现内容的加速。&lt;/p&gt;&#10;&lt;h2 id="x20-开播到看播的流程"&gt;开播到看播的流程&lt;/h2&gt;&#10;&lt;p&gt;一个主播启动直播，到用户进行播放，这中间发生过什么事？为什么需要直播分发和直播加速？&lt;/p&gt;&#10;&lt;h3 id="推流端"&gt;推流端&lt;/h3&gt;&#10;&lt;p&gt;主播通过直播APP主动发起直播请求，直播APP启动音视频采集，然后采集的数据编码成H264/AAC，接下来封装成推流协议的数据格式，把数据推送到边缘服务器。&lt;/p&gt;&#10;&lt;h4 id="采集"&gt;采集&lt;/h4&gt;&#10;&lt;p&gt;一般通过手机摄像头或者采集卡采集到视频数据，原始数据是一幅幅的图片，通过硬件编码，编码成H264/H265格式的视频数据。&lt;/p&gt;&#10;&lt;p&gt;音频是通过麦克风采集，通过硬件编码，编码成AAC格式的音频数据。&lt;/p&gt;&#10;&lt;h4 id="推流协议封装"&gt;推流协议封装&lt;/h4&gt;&#10;&lt;p&gt;直播APP根据使用的推流协议，把编码后的视频数据和音频数据封装成推流协议需要的格式&lt;/p&gt;&#10;&lt;h4 id="推流-1"&gt;推流&lt;/h4&gt;&#10;&lt;p&gt;直播APP通过推流域名获取到最优的推流服务器，使用推流协议连接上推流服务器，把封装好的数据推送到服务器。&lt;/p&gt;&#10;&lt;p&gt;一般云厂商会做DNS负载调度，根据访问DNS服务器的IP，选择相同运营商，距离最近，负载最轻的服务器。&lt;/p&gt;&#10;&lt;p&gt;选择合适的推流协议，选择合适的推流服务器，都是为了解决&lt;code&gt;第一公里&lt;/code&gt;的问题。&lt;/p&gt;&#10;&lt;h3 id="推流服务器"&gt;推流服务器&lt;/h3&gt;&#10;&lt;h4 id="解封装"&gt;解封装&lt;/h4&gt;&#10;&lt;p&gt;推流服务器根据推流协议，跟直播APP建立连接，接收推送过来的数据，并且解开封装，得到音频和视频数据，以及Meta信息。&lt;/p&gt;&#10;&lt;h4 id="通知索引服务器"&gt;通知索引服务器&lt;/h4&gt;&#10;&lt;p&gt;推流服务器接收到一路推流后，会通知索引服务器，并带上流的各种信息。&lt;/p&gt;&#10;&lt;p&gt;索引服务器收到通知后，会记录这种流，以便其他服务器过来查寻时，能够返回正确的推流服务器地址。&lt;/p&gt;&#10;&lt;p&gt;索引服务器也可以通知业务服务器，新增加了一路直播流，直播APP就可以刷新看到这路新的流。&lt;/p&gt;&#10;&lt;h3 id="播放端"&gt;播放端&lt;/h3&gt;&#10;&lt;h4 id="播放"&gt;播放&lt;/h4&gt;&#10;&lt;p&gt;直播APP刷新后，出现了新推上来的直播流，点击播放，直播APP通过直播流的域名，获取到最优的播放服务器。&lt;/p&gt;&#10;&lt;p&gt;直播APP根据获取到的播放服务器地址，使用播放协议（HLS/RTMP/HTTP-FLV）建立连接，接收数据。&lt;/p&gt;&#10;&lt;p&gt;播放器接收到数据后，进行解封装，解编码格式，渲染输出画面，用户就能看到界面&lt;/p&gt;&#10;&lt;h4 id="最后一公里"&gt;最后一公里&lt;/h4&gt;&#10;&lt;p&gt;&lt;code&gt;最后一公里&lt;/code&gt;是指播放服务器到用户端的过程。&lt;/p&gt;&#10;&lt;p&gt;直播APP根据播放域名，从DNS服务器获取最优的播放服务器地址（也有可能是通过访问负载均衡服务器）进行播放；而DNS服务器（或者负载均衡服务器）需要从运营商，IP距离，负载情况等多方面考虑，选择最优的一个服务器。&lt;/p&gt;&#10;&lt;p&gt;同时，直播APP要根据业务需求，用户网络环境等选择合适的播放协议进行播放。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;最后一公里&lt;/code&gt;需要直播系统和直播APP，业务设计等多方面共同参与优化，给用户最好的体验。&lt;/p&gt;&#10;&lt;h3 id="CDN"&gt;CDN&lt;/h3&gt;&#10;&lt;h4 id="回源-1"&gt;回源&lt;/h4&gt;&#10;&lt;p&gt;当直播APP向边缘节点（DNS返回的播放服务器）请求这个直播流时，边缘节点会先检测本地有没有这个流；如果没有找到，则需要向回源服务器请求数据。&lt;/p&gt;&#10;&lt;p&gt;边缘节点接收到回源数据后，马上会返回给直播APP，这样用户就可以及时看到画面。&lt;/p&gt;&#10;&lt;h4 id="中转"&gt;中转&lt;/h4&gt;&#10;&lt;p&gt;边缘节点的运营商，可能跟推流服务器的运营商不一样，这时候就需要中转服务器来处理。&lt;/p&gt;</description></item><item><title>音视频基础- 流媒体协议</title><link>https://www.oneyearago.me/2024/03/10/vedio-basic-06/</link><pubDate>Sun, 10 Mar 2024 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2024/03/10/vedio-basic-06/</guid><description>&lt;h2 id="流媒体协议"&gt;流媒体协议&lt;/h2&gt;&#10;&lt;h3 id="HLS"&gt;HLS&lt;/h3&gt;&#10;&lt;p&gt;HLS 全称是 HTTP Live Streaming，是一个由 Apple 公司提出的基于 HTTP 的媒体流传输协议，用于实时音视频流的传输。目前HLS协议被广泛的应用于视频点播和直播领域。&lt;/p&gt;&#10;&lt;p&gt;HLS协议将整条流切割成一个小的能够经过HTTP下载的媒体文件，而后提供一个配套的媒体列表文件M3U8。客户端拿到M3U8后，根据内容顺序地拉取媒体文件播放。&lt;/p&gt;&#10;&lt;p&gt;HLS只请求基本的HTTP报文，可以穿过任何允许HTTP数据通过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。&lt;/p&gt;&#10;&lt;h4 id="HLS协议"&gt;HLS协议&lt;/h4&gt;&#10;&lt;p&gt;HLS 协议由三部分组成：HTTP、M3U8、TS。其中，HTTP是传输协议，M3U8是索引文件，TS是音视频的承载文件&lt;/p&gt;&#10;&lt;h5 id="HTTP传输"&gt;HTTP传输&lt;/h5&gt;&#10;&lt;p&gt;HTTP是最常见的应用层协议，我们日常基本的浏览器上网就是基于HTTP协议。&lt;/p&gt;&#10;&lt;p&gt;在HLS中，客户端通过HTTP请求获取m3u8内容和TS文件。&lt;/p&gt;&#10;&lt;h5 id="M3U8"&gt;M3U8&lt;/h5&gt;&#10;&lt;p&gt;M3U8是一个UTF-8文本文件，不包含音视频数据，是一个索引文件。&lt;/p&gt;&#10;&lt;p&gt;M3U8的内容由一些属性列表和URI组成：属性列表描述整个流的一些信息或者单个TS的信息；URI描述TS的路径。&lt;/p&gt;&#10;&lt;h5 id="TS"&gt;TS&lt;/h5&gt;&#10;&lt;p&gt;TS文件是一个媒体段，包含一部分音视频数据。单个TS可以单独播放。&lt;/p&gt;&#10;&lt;p&gt;一个完整的视频会包含很多个ts文件，直播场景下会不断的产生新的ts文件。&lt;/p&gt;&#10;&lt;p&gt;播放HLS时实际上播放的就是TS文件。&lt;/p&gt;&#10;&lt;h4 id="HLS优点和缺点"&gt;HLS优点和缺点&lt;/h4&gt;&#10;&lt;p&gt;优点：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;手机，平板，电脑等设备对HLS的支持比较好&lt;/li&gt;&#10;&lt;li&gt;HLS可以根据网络带宽和设备性能等因素，自动调整视频的码率和分辨率，以保证最佳的观看体验。&lt;/li&gt;&#10;&lt;li&gt;HTTP 协议网络兼容性好, HTTP 数据包也可以方便地通过防火墙或者代理服务器, CDN 支持良好.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;缺点：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;由于HLS是基于分段传输的，因此无法实现实时性较高的直播，不适合对实时性要求较高的场景，通常延迟都在10-30s之间。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="RTMP"&gt;RTMP&lt;/h3&gt;&#10;&lt;p&gt;RTMP 协议是Real Time Message Protocol(实时信息传输协议)的缩写，它是由Adobe公司提出的一种应用层的协议，用来解决多媒体数据传输流的多路复用（Multiplexing）和分包（packetizing）的问题。&lt;/p&gt;&#10;&lt;p&gt;RTMP协议是应用层协议，是要靠底层可靠的传输层协议（通常是TCP）来保证信息传输的可靠性的。一般是使用端口1935，并且无论传输何种信息包括音视频数据都只有一个socket通道即只有一个物理通道。&lt;/p&gt;&#10;&lt;p&gt;RTMP协议传输时会对数据做自己的格式化，这种格式的消息我们称之为RTMP Message，而实际传输的时候为了更好地实现多路复用、分包和信息的公平性，发送端会把Message划分为带有Message ID的Chunk，每个Chunk可能是一个单独的Message，也可能是Message的一部分，在接受端会根据chunk中包含的data的长度，message id和message的长度把chunk还原成完整的Message，从而实现信息的收发。&lt;/p&gt;&#10;&lt;h4 id="RTMP协议"&gt;RTMP协议&lt;/h4&gt;&#10;&lt;p&gt;RTMP在通讯前，需要建立客户端和服务器之间的连接，并且需要经过握手验证连接才能建立连接。&lt;/p&gt;&#10;&lt;p&gt;连接后，RTMP通过消息（Message）来传输数据。消息可以包括音频、视频、数据，甚至其它任何信息。&lt;/p&gt;&#10;&lt;p&gt;为了优化传输，Message会分为更小的单位：块。&lt;/p&gt;&#10;&lt;h5 id="握手"&gt;握手&lt;/h5&gt;&#10;&lt;p&gt;RTMP建立连接前，需要握手。&lt;/p&gt;&#10;&lt;p&gt;RTMP的握手分为简单握手和复杂握手：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;简单握手不需要进行验证&lt;/li&gt;&#10;&lt;li&gt;复杂握手要通过复杂握手协议进行必要的验证&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="组块"&gt;组块&lt;/h5&gt;&#10;&lt;p&gt;RTMP会把消息（Message）分成小的块（Chunk）来传输，接收端通过消息ID来重组消息。&lt;/p&gt;&#10;&lt;h5 id="AMF"&gt;AMF&lt;/h5&gt;&#10;&lt;p&gt;RTMP使用AMF协议描述消息内容。&lt;/p&gt;&#10;&lt;h5 id="消息"&gt;消息&lt;/h5&gt;&#10;&lt;p&gt;RTMP的消息有几种：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;协议控制消息\&lt;br&gt;&#10;协议控制消息负责交换参数，设置参数等。&lt;/li&gt;&#10;&lt;li&gt;命令消息\&lt;br&gt;&#10;命令消息是控制流的，创建，连接，推流，拉流，关闭流等。&lt;/li&gt;&#10;&lt;li&gt;音视频消息\&lt;br&gt;&#10;音视频消息携带音视频数据，一般是FLV格式封装的数据&lt;/li&gt;&#10;&lt;li&gt;数据消息\&lt;br&gt;&#10;数据消息主要传输Meta数据，比如分辨率，名称等&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h4 id="RTMP优点和缺点"&gt;RTMP优点和缺点&lt;/h4&gt;&#10;&lt;p&gt;优点：&lt;/p&gt;</description></item><item><title>webrtc 03 - 常见webrtc流媒体服务器的设计</title><link>https://www.oneyearago.me/2024/03/03/webrtc_03/</link><pubDate>Sun, 03 Mar 2024 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2024/03/03/webrtc_03/</guid><description>&lt;h2 id="x20-流行的-webrtc-流媒体服务器"&gt;流行的 webrtc 流媒体服务器&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;licode&lt;/li&gt;&#10;&lt;li&gt;janus&lt;/li&gt;&#10;&lt;li&gt;mesooze&lt;/li&gt;&#10;&lt;li&gt;mediasoup&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;整体设计&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;优缺点&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/stream_server/licode.png" alt="image"&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;完全支持 WebRTC,文档和教程有限，性能可能表现不佳&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/stream_server/janus.png" alt="image"&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;高度灵活插件和扩展，支持多种通信模式，依赖特定版本的外部库或系统，升级和维护时可能会遇到兼容性问题&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/stream_server/medooze.png" alt="image"&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;高性能、支持webrtc，可扩展性强，文档和教程较少，用户界面和管理工具缺乏&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/stream_server/mediasoup.png" alt="image"&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;高度可扩展、模块化设计、低延迟，存在一定学习曲线，虽然灵活可配置但是需要对应技术知识&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="x20-webrtc流媒体服务器和-SRS的区别和联系"&gt;webrtc流媒体服务器和 SRS的区别和联系&lt;/h2&gt;&#10;&lt;p&gt;Mediasoup 和 SRS（Simple Real-time Server）都是流媒体服务框架，但它们有一些关键的区别，主要体现在架构、功能和使用场景上：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;目标和功能&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Mediasoup&lt;/strong&gt;：主要用于构建可扩展的多方视频会议应用。它支持WebRTC协议，专注于低延迟的音视频通讯。Mediasoup 提供了一个服务器端库，用来处理大规模的媒体传输，不包含信号部分，通常需要与其他WebRTC信号组件（如WebSocket服务器）结合使用。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;SRS&lt;/strong&gt;：是一个更通用的实时视频服务器，支持RTMP, HLS, WebRTC, SRT等多种协议。它可以用于直播流的推送、转码、分发等多种场景。SRS 的功能比 Mediasoup 广泛，涵盖了从直播到点播的多种流媒体应用。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;架构和性能&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Mediasoup&lt;/strong&gt;：使用现代C++编写，重点优化了多核处理能力，适合处理大量的并发媒体流。Mediasoup 设计上更偏向于构建模块化、微服务架构的应用。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;SRS&lt;/strong&gt;：也是使用C++编写，但架构更传统，侧重于高效的媒体流处理和较低的资源消耗。SRS 在设计上更易于部署和维护，特别是在直播场景下。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Mediasoup&lt;/strong&gt;：由于其对WebRTC的深度支持和高性能，非常适合用于构建需要高并发、实时互动的视频会议系统或类似应用。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;SRS&lt;/strong&gt;：适用于需要支持多种协议和广泛应用的直播系统，如在线教育、游戏直播、企业直播等。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;社区和支持&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Mediasoup&lt;/strong&gt;：有一个活跃的开发社区，主要集中在GitHub上。社区成员通常专注于实时通信和视频会议解决方案的优化。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;SRS&lt;/strong&gt;：也有一个非常活跃的社区，支持问题更广泛，从服务器配置到流媒体优化等多方面都有讨论。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;如果你的应用需要高效的视频通信和WebRTC支持，Mediasoup可能是更好的选择；如果你需要一个多功能且易于扩展的直播服务平台，SRS可能更适合。二者有融合趋势。&lt;/p&gt;</description></item><item><title>webrtc 02 - NAT网络和P2P打洞</title><link>https://www.oneyearago.me/2024/02/15/webrtc_02/</link><pubDate>Thu, 15 Feb 2024 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2024/02/15/webrtc_02/</guid><description>&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/webrtc/NAT/nat_00.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h2 id="x20-NAT-原理"&gt;NAT 原理&lt;/h2&gt;&#10;&lt;p&gt;NAT（网络地址转换）主要用于在本地网络和外部网络之间转换IP地址。它在改善IP地址利用率和网络安全方面起到了重要作用。NAT有几种主要类型，每种类型在处理网络流量和安全方面都有不同的特点。以下是详细介绍：&lt;/p&gt;&#10;&lt;h3 id="1-全锥形NAT（Full-Cone-NAT）"&gt;1. 全锥形NAT（Full Cone NAT）&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/webrtc/NAT/nat_01.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="特点："&gt;特点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;所有外部主机&lt;/strong&gt;都可以通过相同的外部IP地址和端口访问NAT内部的设备。&lt;/li&gt;&#10;&lt;li&gt;只要端口映射建立，任何外部主机都可以通过这个端口访问内部设备。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="工作原理："&gt;工作原理：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;一旦内部设备向外部主机发送了一个数据包，NAT设备会为该会话创建一个映射，记录内部IP地址和端口以及外部IP地址和端口。&lt;/li&gt;&#10;&lt;li&gt;任何外部主机可以通过这个外部端口发送数据包到内部设备。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="优点："&gt;优点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;兼容性好，适合需要多设备互联的应用（如视频会议）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="缺点："&gt;缺点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;安全性相对较差，容易受到外部攻击。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="2-地址受限锥形NAT（Addr-Restricted-Cone-NAT）"&gt;2. 地址受限锥形NAT（Addr Restricted Cone NAT）&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/webrtc/NAT/nat_02.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="特点：-1"&gt;特点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;只有收到内部设备数据包的外部主机，才能通过映射的外部端口访问内部设备。&lt;/li&gt;&#10;&lt;li&gt;IP地址限制，任何未接收过内部设备数据包的外部IP地址，无法通过该端口访问内部设备。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="工作原理：-1"&gt;工作原理：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;当内部设备向外部主机发送一个数据包时，NAT设备会创建一个映射。&lt;/li&gt;&#10;&lt;li&gt;只有当外部主机的IP地址与最初的外部主机IP地址相同时，数据包才能通过映射端口访问内部设备。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="优点：-1"&gt;优点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;安全性相对较好，限制了外部主机的访问。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="缺点：-1"&gt;缺点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;兼容性稍差于全锥形NAT，某些应用可能需要额外配置。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="3-端口受限锥形NAT（Port-Restricted-Cone-NAT）"&gt;3. 端口受限锥形NAT（Port Restricted Cone NAT）&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/webrtc/NAT/nat_03.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="特点：-2"&gt;特点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;类似于受限锥形NAT，但进一步限制了端口号。&lt;/li&gt;&#10;&lt;li&gt;只有收到内部设备数据包的外部主机和端口才能通过映射的外部端口访问内部设备。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="工作原理：-2"&gt;工作原理：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;当内部设备向外部主机的特定端口发送一个数据包时，NAT设备会创建一个映射。&lt;/li&gt;&#10;&lt;li&gt;只有当外部主机的IP地址和端口都与最初的数据包目标匹配时，数据包才能通过映射端口访问内部设备。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="优点：-2"&gt;优点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;安全性更高，进一步限制了外部主机和端口的访问。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="缺点：-2"&gt;缺点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;兼容性较差，某些应用可能无法正常工作。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="4-对称NAT（Symmetric-NAT）"&gt;4. 对称NAT（Symmetric NAT）&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/webrtc/NAT/nat_04.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="特点：-3"&gt;特点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;内部设备对每个外部主机和端口的连接使用不同的外部端口。&lt;/li&gt;&#10;&lt;li&gt;每个会话都有唯一的外部IP地址和端口映射。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="工作原理：-3"&gt;工作原理：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;当内部设备向外部主机发送数据包时，NAT设备会为该特定会话创建一个唯一的映射。&lt;/li&gt;&#10;&lt;li&gt;只有目标外部主机和端口能够通过该特定映射的外部端口访问内部设备。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="优点：-3"&gt;优点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;提供最高的安全性，限制了外部主机和端口的访问。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="缺点：-3"&gt;缺点：&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;兼容性最差，许多P2P应用（如VoIP、视频会议）可能无法正常工作。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="STUN服务器基本原理"&gt;STUN服务器基本原理&lt;/h2&gt;&#10;&lt;p&gt;STUN服务器通过与客户端进行通信，帮助客户端了解其在NAT后的外部IP和端口，以及NAT的行为类型。主要使用STUN协议的Binding Request和Binding Response消息。&lt;/p&gt;&#10;&lt;h3 id="判断NAT类型的步骤"&gt;判断NAT类型的步骤&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;客户端发送初始Binding Request&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;客户端向STUN服务器的固定端口（如3478）发送一个Binding Request。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;服务器返回Binding Response&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;服务器返回一个Binding Response，其中包含服务器看到的客户端的源IP地址和端口号（即外部IP和端口）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;客户端分析初始响应&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;客户端检查返回的外部IP地址和端口号。&lt;/li&gt;&#10;&lt;li&gt;如果外部IP地址和端口号与客户端的本地IP地址和端口号相同，则客户端直接连接到开放的Internet上，没有NAT。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;客户端发送第二个Binding Request&lt;/strong&gt;（通过改变源端口）：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;客户端从不同的本地端口向同一个STUN服务器发送另一个Binding Request。&lt;/li&gt;&#10;&lt;li&gt;如果需要，可以发送到STUN服务器的另一个端口（如果服务器支持多端口监听）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;分析第二个响应&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;如果返回的外部IP地址和端口号与第一次相同，则客户端在完全锥形NAT后面。&lt;/li&gt;&#10;&lt;li&gt;如果外部IP地址相同但端口号不同，则客户端在端口受限锥形NAT后面。&lt;/li&gt;&#10;&lt;li&gt;如果外部IP地址和端口号都不同，则客户端在对称NAT后面。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;</description></item><item><title>webrtc-01 视频采集和录制</title><link>https://www.oneyearago.me/2024/02/06/webrtc_01/</link><pubDate>Tue, 06 Feb 2024 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2024/02/06/webrtc_01/</guid><description>&lt;h2 id="整体架构"&gt;整体架构&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://webrtc.github.io/webrtc-org/architecture/#"&gt;https://webrtc.github.io/webrtc-org/architecture/&lt;/a&gt;&lt;/p&gt;&#10;&lt;h4 id="整体设计图"&gt;整体设计图&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://webrtc.github.io/webrtc-org/assets/images/webrtc-public-diagram-for-website.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h2 id="示例"&gt;示例&lt;/h2&gt;&#10;&lt;h4 id="进行视频采集"&gt;进行视频采集&lt;/h4&gt;&#10;&lt;p&gt;解释放代码注释中, html5 和 webrtc 标准互通 确实让音视频的生态、开发者的处境变得更好，很好的思路，接触音视频有点晚少了很多有意思的尝试。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;body&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;div&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#9;&amp;lt;video autoplay playsinline id=&amp;#34;player&amp;#34;&amp;gt;&amp;lt;/video&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;/div&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;div&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#9;&amp;lt;button id=&amp;#34;record&amp;#34;&amp;gt;Start Record&amp;lt;/button&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#9;&amp;lt;button id=&amp;#34;recplay&amp;#34; disabled&amp;gt;Play&amp;lt;/button&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#9;&amp;lt;button id=&amp;#34;download&amp;#34; disabled&amp;gt;Download&amp;lt;/button&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;/div&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;div&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#9;&amp;lt;video playsinline id=&amp;#34;recplayer&amp;#34;&amp;gt;&amp;lt;/video&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;/div&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;script src=&amp;#34;https://webrtc.github.io/adapter/adapter-latest.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;script src=&amp;#34;https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;script src=&amp;#34;./js/client.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;/body&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;use strict&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;var videoplay = document.querySelector(&amp;#39;video#player&amp;#39;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;//var audioplay = document.querySelector(&amp;#39;audio#audioplayer&amp;#39;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;//record&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;var recvideo = document.querySelector(&amp;#39;video#recplayer&amp;#39;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;var btnRecord = document.querySelector(&amp;#39;button#record&amp;#39;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;var btnPlay = document.querySelector(&amp;#39;button#recplay&amp;#39;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;var btnDownload = document.querySelector(&amp;#39;button#download&amp;#39;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;var buffer;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;var mediaRecorder;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function gotMediaStream(stream){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;window.stream = stream;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;1. html5标准&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9; 2008 年发布，正式包含了 &amp;lt;video&amp;gt; 和 &amp;lt;audio&amp;gt; 元素，这些标准在 2010 年代初得到了广泛的采纳和实施，同时逐渐增加了更多功能，如 srcObject 属性&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;2. WebRTC 标准&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;2013 年左右，WebRTC 开始在主流浏览器中得到实现&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;3. 互通性&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;MediaStream API 是连接 HTML5 媒体标准和 WebRTC 的桥梁。这个 API 允许将从摄像头和麦克风等设备获取的媒体流直接赋值给 &amp;lt;video&amp;gt; 或 &amp;lt;audio&amp;gt; 元素&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;srcObject 属性的引入（初步在 2013 年左右的草案中出现）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;*/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;videoplay.srcObject = stream;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function handleError(err){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;console.log(&amp;#39;getUserMedia error:&amp;#39;, err);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function start() {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;if(!navigator.mediaDevices ||&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;!navigator.mediaDevices.getUserMedia){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;console.log(&amp;#39;getUserMedia is not supported!&amp;#39;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;return;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}else{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;var constraints = {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;video : {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;width: 640,&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;height: 480,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;frameRate:15&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;}, &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;audio : false &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;// getUserMedia(constraints?: MediaStreamConstraints): Promise&amp;lt;MediaStream&amp;gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;navigator.mediaDevices.getUserMedia(constraints)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;.then(gotMediaStream)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;.catch(handleError);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;start();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function handleDataAvailable(e){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;if(e &amp;amp;&amp;amp; e.data &amp;amp;&amp;amp; e.data.size &amp;gt; 0){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9; &#9;buffer.push(e.data);&#9;&#9;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function startRecord(){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;buffer = [];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;var options = {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;mimeType: &amp;#39;video/webm;codecs=vp8&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;if(!MediaRecorder.isTypeSupported(options.mimeType)){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;console.error(`${options.mimeType} is not supported!`);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;return;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;try{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;// https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder/dataavailable_event&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;mediaRecorder = new MediaRecorder(window.stream, options);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}catch(e){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;console.error(&amp;#39;Failed to create MediaRecorder:&amp;#39;, e);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;return;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;mediaRecorder.ondataavailable = handleDataAvailable;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;mediaRecorder.start(10);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;function stopRecord(){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;mediaRecorder.stop();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;btnRecord.onclick = ()=&amp;gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;if(btnRecord.textContent === &amp;#39;Start Record&amp;#39;){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;startRecord();&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;btnRecord.textContent = &amp;#39;Stop Record&amp;#39;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;btnPlay.disabled = true;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;btnDownload.disabled = true;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}else{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;stopRecord();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;btnRecord.textContent = &amp;#39;Start Record&amp;#39;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;btnPlay.disabled = false;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;btnDownload.disabled = false;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;btnPlay.onclick = ()=&amp;gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;var blob = new Blob(buffer, {type: &amp;#39;video/webm&amp;#39;});&#9;// webm 是一种开放的媒体文件格式，专门设计用于互联网上的视频和音频内容 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;WebP 和 WebM 都是由谷歌推动的开源项目，它们都旨在优化网络上的媒体内容传输，但分别用于不同的媒体类型：&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;WebP：是一种主要针对图像的文件格式，支持无损和有损压缩。WebP 提供的压缩效率通常优于传统的 JPEG 和 PNG 格式，&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;并且支持动画和透明度（alpha transparency），这使得它成为一个非常适合网络使用的图像格式。WebP 的目标是减小图像文件的大小，&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;从而加快网页加载速度，并减少带宽使用。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;WebM：是一种视频容器格式，它主要用于包含视频和音频流，特别是在网络应用中。WebM 使用 VP8 或 VP9 作为视频编解码器，Vorbis 或 Opus 为音频编解码器。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;WebM 格式因其开放性和高效的编解码性能，常被用于视频流和网页视频播放。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;*/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;recvideo.src = window.URL.createObjectURL(blob);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;recvideo.srcObject = null;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;recvideo.controls = true;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;recvideo.play();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;btnDownload.onclick = ()=&amp;gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;var blob = new Blob(buffer, {type: &amp;#39;video/webm&amp;#39;});&#9;// https://zh.wikipedia.org/wiki/%E4%BA%92%E8%81%94%E7%BD%91%E5%AA%92%E4%BD%93%E7%B1%BB%E5%9E%8B#Type_video&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;var url = window.URL.createObjectURL(blob);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;var a = document.createElement(&amp;#39;a&amp;#39;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;a.href = url;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;a.style.display = &amp;#39;none&amp;#39;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;a.download = &amp;#39;aaa.webm&amp;#39;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;a.click();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="demo部署"&gt;demo部署&lt;/h4&gt;&#10;&lt;p&gt;备注: 需要https证书&lt;/p&gt;</description></item><item><title>音视频基础- rtmp协议</title><link>https://www.oneyearago.me/2023/06/06/vedio-basic-04/</link><pubDate>Tue, 06 Jun 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/06/06/vedio-basic-04/</guid><description>&lt;h3 id="RTMP"&gt;RTMP&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/rtmp_01.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/rtmp_02.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/rtmp_03.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/rtmp_04.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="推流-拉流"&gt;推流 拉流&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/rtmp_05.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/rtmp_06.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/rtmp_07.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="消息格式"&gt;消息格式&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/rtmp_08.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/rtmp_09.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="FLV-协议"&gt;FLV 协议&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/rtmp/flv_01.png" alt="image"&gt;&lt;/p&gt;</description></item><item><title>音视频基础- 直播系统</title><link>https://www.oneyearago.me/2023/06/05/vedio-basic-05/</link><pubDate>Mon, 05 Jun 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/06/05/vedio-basic-05/</guid><description>&lt;h3 id="直播系统简述"&gt;直播系统简述&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/live/live_01.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/live/live_02.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/live/live_03.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/live/live_04.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/live/live_05.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/live/live_06.png" alt="image"&gt;&lt;/p&gt;</description></item><item><title>音视频基础- H264码流结构</title><link>https://www.oneyearago.me/2023/06/02/vedio-basic-03/</link><pubDate>Fri, 02 Jun 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/06/02/vedio-basic-03/</guid><description>&lt;h3 id="H264码流结构"&gt;H264码流结构&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_trans_01.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_trans_02.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_trans_03.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_trans_04.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_trans_05.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_trans_06.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="案例"&gt;案例&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_trans_07.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_trans_08.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_trans_09.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="SPS-amp-PPS"&gt;SPS &amp;amp; PPS&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/spspps/spspps_01.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/spspps/spspps_02.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/spspps/spspps_03.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/spspps/spspps_04.png" alt="image"&gt;&lt;/p&gt;</description></item><item><title>音视频基础- H264视频编码</title><link>https://www.oneyearago.me/2023/04/26/vedio-basic-02/</link><pubDate>Wed, 26 Apr 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/04/26/vedio-basic-02/</guid><description>&lt;h2 id="有损压缩"&gt;有损压缩&lt;/h2&gt;&#10;&lt;h3 id="帧内压缩"&gt;帧内压缩&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_01.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_02.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_03.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_04.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_05.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_06.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_07.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_08.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_09.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_10.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_11.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_12.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_13.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_14.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_15.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="帧间压缩"&gt;帧间压缩&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_16.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_17.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_18.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_19.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_20.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_21.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="案例"&gt;案例&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_22.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_23.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h2 id="无损压缩"&gt;无损压缩&lt;/h2&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_24.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_25.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_26.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/H264/h264_codec_27.png" alt="image"&gt;&lt;/p&gt;</description></item><item><title>音视频基础-视频基础-01</title><link>https://www.oneyearago.me/2023/04/19/vedio-basic-01/</link><pubDate>Wed, 19 Apr 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/04/19/vedio-basic-01/</guid><description>&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/vedio_basic_01.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/vedio_basic_02.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/vedio_basic_03.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/vedio_basic_04.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/vedio_basic_05.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/vedio_basic_06.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/vedio_basic_07.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/vedio_basic_08.png" alt="image"&gt;&lt;/p&gt;</description></item><item><title>音视频基础-音频基础-04</title><link>https://www.oneyearago.me/2023/04/13/audio-basic-04/</link><pubDate>Thu, 13 Apr 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/04/13/audio-basic-04/</guid><description>&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_codec_1.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_codec_2.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_codec_3.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_codec_4.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_codec_5.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_codec_6.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="音频重采样"&gt;音频重采样&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_codec_7.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_codec_8.png" alt="image"&gt;&lt;/p&gt;</description></item><item><title>音视频基础-音频基础-03</title><link>https://www.oneyearago.me/2023/04/12/audio-basic-03/</link><pubDate>Wed, 12 Apr 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/04/12/audio-basic-03/</guid><description>&lt;h3 id="音频编码基本原理"&gt;&lt;strong&gt;音频编码基本原理&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;h3 id="（1）-音频信号的冗余信息"&gt;&lt;strong&gt;（1） 音频信号的冗余信息&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;p&gt;数字音频信号如果不加压缩地直接进行传送，将会占用极大的带宽。例如，一套双声道数字音频若取样频率为44.1KHz，每样值按16bit量化，则其码率为：&lt;/p&gt;&#10;&lt;p&gt;2*44.1kHz*16bit=1.411Mbit/s&lt;/p&gt;&#10;&lt;p&gt;如此大的带宽将给信号的传输和处理都带来许多困难，因此必须采取音频压缩技术对音频数据进行处理，才能有效地传输音频数据。&lt;/p&gt;&#10;&lt;p&gt;数字音频压缩编码在保证信号在听觉方面不产生失真的前提下，对音频数据信号进行尽可能大的压缩。数字音频压缩编码采取去除声音信号中冗余成分的方法来实现。所谓冗余成分指的是音频中不能被人耳感知到的信号，它们对确定声音的音色，音调等信息没有任何的帮助。&lt;/p&gt;&#10;&lt;p&gt;冗余信号包含人耳听觉范围外的音频信号以及被掩蔽掉的音频信号等。例如，人耳所能察觉的声音信号的频率范围为20Hz～20KHz，除此之外的其它频率人耳无法察觉，都可视为冗余信号。此外，根据人耳听觉的生理和心理声学现象，当一个强音信号与一个弱音信号同时存在时，弱音信号将被强音信号所掩蔽而听不见，这样弱音信号就可以视为冗余信号而不用传送。这就是人耳听觉的掩蔽效应，主要表现在&lt;a href="https://so.csdn.net/so/search?q=%E9%A2%91%E8%B0%B1&amp;amp;spm=1001.2101.3001.7020"&gt;频谱&lt;/a&gt;掩蔽效应和时域掩蔽效应，现分别介绍如下：&lt;/p&gt;&#10;&lt;h4 id="（a）频谱掩蔽效应"&gt;（a）频谱掩蔽效应&lt;/h4&gt;&#10;&lt;p&gt;一个频率的声音能量小于某个阈值之后，人耳就会听不到，这个阈值称为最小可闻阈。当有另外能量较大的声音出现的时候，该声音频率附近的阈值会提高很多，即所谓的掩蔽效应。如图所示：&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_03_01.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;频率掩蔽效应&lt;/p&gt;&#10;&lt;p&gt;由图中我们可以看出人耳对2KHz～5KHz的声音最敏感，而对频率太低或太高的声音信号都很迟钝，当有一个频率为0.2KHz、强度为60dB的声音出现时，其附近的阈值提高了很多。由图中我们可以看出在0.1KHz以下、1KHz以上的部分,由于离0.2KHz强信号较远，不受0.2KHz强信号影响,阈值不受影响；而在0.1KHz～1KHz范围，由于0.2KHz强音的出现,阈值有较大的提升，人耳在此范围所能感觉到的最小声音强度大幅提升。如果0.1KHz～1KHz范围内的声音信号的强度在被提升的阈值曲线之下，由于它被0.2KHz强音信号所掩蔽，那么此时我们人耳只能听到0.2KHz的强音信号而根本听不见其它弱信号，这些与0.2KHz强音信号同时存在的弱音信号就可视为冗余信号而不必传送。&lt;/p&gt;&#10;&lt;h4 id="（b）-时域掩蔽效应"&gt;（b） 时域掩蔽效应&lt;/h4&gt;&#10;&lt;p&gt;当强音信号和弱音信号同时出现时，还存在时域掩蔽效应。即两者发生时间很接近的时候，也会发生掩蔽效应。时域掩蔽过程曲线如图所示，分为前掩蔽、同时掩蔽和后掩蔽三部分。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_03_02.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;时域掩蔽效应&lt;/p&gt;&#10;&lt;p&gt;由图我们可以看出，时域掩蔽效应可以分成三种：前掩蔽，同时掩蔽，后掩蔽。前掩蔽是指人耳在听到强信号之前的短暂时间内，已经存在的弱信号会被掩蔽而听不到。同时掩蔽是指当强信号与弱信号同时存在时，弱信号会被强信号所掩蔽而听不到。后掩蔽是指当强信号消失后，需经过较长的一段时间才能重新听见弱信号，称为后掩蔽。这些被掩蔽的弱信号即可视为冗余信号。&lt;/p&gt;&#10;&lt;h3 id="（2）-压缩编码方法"&gt;&lt;strong&gt;（2） 压缩编码方法&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;p&gt;当前数字音频编码领域存在着不同的编码方案和实现方式, 但基本的编码思路大同小异, 如图所示。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_03_03.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;数字音频编码系统模型&lt;/p&gt;&#10;&lt;p&gt;对每一个音频声道中的音频采样信号,首先都要将它们映射到频域中,这种时域到频域的映射可通过子带滤波器实现。每个声道中的音频采样块首先要根据心理声学模型来计算掩蔽门限值, 然后由计算出的掩蔽门限值决定从公共比特池中分配给该声道的不同频率域中多少比特数，接着进行量化以及编码工作，最后将控制参数及辅助数据加入数据之中，产生编码后的数据流。&lt;/p&gt;&#10;&lt;h4 id="引用"&gt;引用&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.csdn.net/leixiaohua1020/article/details/28114081"&gt;https://blog.csdn.net/leixiaohua1020/article/details/28114081&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>音视频基础-音频基础-02</title><link>https://www.oneyearago.me/2023/04/09/audio-basic-02/</link><pubDate>Sun, 09 Apr 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/04/09/audio-basic-02/</guid><description>&lt;h3 id="怎样对声音进行数字化？"&gt;怎样对声音进行数字化？&lt;/h3&gt;&#10;&lt;p&gt;对声音进行数字化，首先要使用特定的设备对声音进行采集，比如麦克风就是常见的声音采集设备。麦克风里面有一层碳膜，非常薄而且十分敏感。声音是一种纵波，会压缩空气也会压缩这层碳膜，碳膜在受到挤压时也会发出振动，在碳膜的下方就是一个电极，碳膜在振动的时候会接触电极，接触时间的长短和频率与声波的振动幅度和频率有关，这样就完成了声音信号到电信号的转换。之后再经过放大电路处理，就可以实施后面的采样、量化处理了。&lt;/p&gt;&#10;&lt;p&gt;声音由波形组成，包含了不同频率、振幅的波的叠加。为了在数字媒体内表示这些波形，需要对波形进行&lt;strong&gt;采样&lt;/strong&gt;，其采样率需要满足可以表示的声音的最高频率；同时还需要存储足够的&lt;strong&gt;位深&lt;/strong&gt;，以表示声音样本中波形的适当振幅。&lt;/p&gt;&#10;&lt;p&gt;声音处理设备重建频率的能力称为其&lt;strong&gt;频率响应&lt;/strong&gt;，创造适当响度和柔度的能力称为其&lt;strong&gt;动态范围&lt;/strong&gt;，这些术语通常统称为声音设备的&lt;strong&gt;保真度&lt;/strong&gt;。最简单的编码方式可以利用这两个基本元素重建声音，同时还能够高效地存储和传输数据。&lt;/p&gt;&#10;&lt;p&gt;声音的数字化过程是将模拟信号（连续时间信号）转化为数字信号（离散时间信号）的过程，包括 3 个步骤：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;采样&lt;/strong&gt;：以一定采样率在时域内获取离散信号。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;量化&lt;/strong&gt;：每个采样点幅度的数字化表示。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;编码&lt;/strong&gt;：以一定格式存储数据。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/how-to-desc-audio.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;经过数字化处理后的数字音频包含如下三要素：&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio-desc.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="采样率"&gt;采样率&lt;/h3&gt;&#10;&lt;p&gt;对模拟信号的采样一般遵循奈奎斯特采样定理：如果一个信号是带限的（即它的傅立叶变换在某一有限频带范围以外均为零），并且它的样本取得足够密（相对于信号中的最高频率而言），那么这些样本值就能唯一地用来表征这一信号，并且能从这些样本中把信号完全恢复出来。为了不失真地恢复模拟信号，采样频率应该不小于模拟信号频谱中最高频率的 2 倍。一般实际应用中保证采样频率为信号最高频率的 2.56～4 倍。&lt;/p&gt;&#10;&lt;p&gt;数字信号由模拟信号采样而来，如果满足采样定理，数字信号就可以完全恢复原始的模拟信号。&lt;/p&gt;&#10;&lt;p&gt;从发声的角度来看，人类发出的声音信号频率绝大部分在 5k Hz 以内，因此以 10k Hz 的频率来采样就足够了。&lt;/p&gt;&#10;&lt;p&gt;生活中常见的采样率：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;8,000 Hz：电话所用采样率，对于人的说话已经足够；&lt;br&gt;&#10;11,025 Hz：AM 调幅广播所用采样率；&lt;br&gt;&#10;22,050 Hz 和 24,000 Hz：FM调频广播所用采样率；&lt;br&gt;&#10;32,000 Hz：miniDV 数码视频 camcorder、DAT（LP mode）所用采样率；&lt;br&gt;&#10;44,100 Hz：音频 CD，也常用于 MPEG-1 音频（VCD/SVCD/MP3）所用采样率；&lt;br&gt;&#10;47,250 Hz：商用 PCM 录音机所用采样率；&lt;br&gt;&#10;48,000 Hz：miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率；&lt;br&gt;&#10;50,000 Hz：商用数字录音机所用采样率；&lt;br&gt;&#10;96,000 或者 192,000 Hz：DVD-Audio、一些 LPCM DVD 音轨、BD-ROM（蓝光盘）音轨、和 HD-DVD（高清晰度 DVD）音轨所用所用采样率；&lt;br&gt;&#10;2.8224 MHz：Direct Stream Digital 的 1 位 sigma-delta modulation 过程所用采样率。&lt;/p&gt;&#10;&lt;/blockquote&gt;</description></item><item><title>音视频基础-音频基础-01</title><link>https://www.oneyearago.me/2023/04/06/audio_basic_01/</link><pubDate>Thu, 06 Apr 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/04/06/audio_basic_01/</guid><description>&lt;blockquote&gt;&#10;&lt;p&gt;本文基本逻辑：声音的定义是什么 → 声音有哪些特征 → 怎样对声音进行数学描述 → 怎样对声音进行数字化 → 数字音频数据是什么&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;『声音』是我们司空见惯再熟悉不过的一种物理现象。我们唱歌发出声音，用耳朵听到声音，用手机记录并分享声音；如果作为音视频开发人员，我们还会在工作中处理众多声音数据。但是，你真的了解『声音』吗？&lt;/p&gt;&#10;&lt;p&gt;如果你自信满满，心想『当然了』，那可以试试回答这个问题： &lt;strong&gt;从我们耳朵听见的『声音』，到我们用手机、电脑所处理的『音频数据』，其中经历了什么？&lt;/strong&gt; 如果你细思起来，感觉还有疑问，不妨继续读下去，和我们一起略略探讨一下：日常开发工作中处理的音频数据，是如何从一种物理现象转变而来。这个探讨也许无用，但可能会有趣。&lt;/p&gt;&#10;&lt;p&gt;探讨这个问题，至少包含了两个大的认知过程：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;1）用科学研究的方法对一个日常现象进行物理定义、特征探索、规律发现、数学描述的过程；&lt;/li&gt;&#10;&lt;li&gt;2）用信息处理手段对物理现象进行数字化的过程。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;当我们用这样的视角回头去看这个问题时，也许可以把它细分成下面几个子问题：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;声音的定义是什么？我们需要通过下定义来界定一种物理现象的范围，才好继续研究下去。&lt;/li&gt;&#10;&lt;li&gt;声音的定义是什么？我们需要通过下定义来界定一种物理现象的范围，才好继续研究下去。&lt;/li&gt;&#10;&lt;li&gt;怎样对声音进行数学描述？数学是描述物理现象、探索物理规律最好的语言。对物理现象的数学描述也是将其进一步数字信息化的基础。&lt;/li&gt;&#10;&lt;li&gt;怎样对声音进行数字化？数字化是物理世界通向信息世界的手段。&lt;/li&gt;&#10;&lt;li&gt;怎样对声音进行数字化？数字化是物理世界通向信息世界的手段。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="1、声音的定义是什么？"&gt;1、声音的定义是什么？&lt;/h3&gt;&#10;&lt;p&gt;『声音』是振动产生的声波，通过介质（气体、固体、液体）传播并能被人或动物听觉器官所感知的波动现象。&lt;/p&gt;&#10;&lt;p&gt;以上便是声音的定义，它将声音界定为一种波动现象，这样就可以针对性的在『波』这个物理概念的范畴里去研究它。当然，如果我们在研究中有新的发现，能颠覆原有的认知，从而重新定义它，也不是没有可能。&lt;/p&gt;&#10;&lt;p&gt;声音是由物体振动所产生。在振动介质(空气、液体或固体)中某一质点在平衡位置附近来回发生振动，并带动周围的质点也发生振动.&lt;/p&gt;&#10;&lt;p&gt;具体来说，当振动物体向外移动时，它会推挤紧挨着它的空气分子，使这些分子向外移动并挤压邻近的空气分子，造成一个压力增高的区域，即压缩波。相反，当振动物体向内移动时，它留下了一个空间，使得周围的空气分子向这个空间移动，填补空缺，从而在该区域产生一个压力降低的区域，即稀疏波。&lt;/p&gt;&#10;&lt;p&gt;这种压缩和稀疏的波动沿着介质传播，形成了声波。因此，周围的质点会因为振动源的振动而被带动，同样开始在其平衡位置附近振动。这就是声音如何在介质中传播的基本原理。每个质点都不会离开其原始位置太远，它们只是局部地来回振动，而声音的传播是通过这种振动的能量和动量在介质中的传递实现的。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_ft.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/Anatomy_of_the_Human_Ear.svg.png" alt="image"&gt;&lt;br&gt;&#10;[图片] 人耳解剖结构图&lt;/p&gt;&#10;&lt;p&gt;要提取声音的特征，首先要感知到它，人类的听觉感知系统是一个复杂的系统，如下图所示。它是怎么感知声音的呢？简单来讲，声音作为一种机械波，通过空气传播到人耳，在人耳中转变为神经动作电位，神经脉冲到达大脑，人从而感知到声音。至于具体细节，我们就不在这里做过多探讨了。&lt;/p&gt;&#10;&lt;h3 id="2、声音有哪些特征？"&gt;2、声音有哪些特征？&lt;/h3&gt;&#10;&lt;p&gt;声音的特征是我们在感知声音并不断对其现象进行研究的过程中逐步识别和提取出来的。比如，我们很容易就能感知到声音有大有小；有尖锐有浑厚；不同的人说话，即使声音大小差不多，我们也能识别他们。我们对这些感知进行总结便提取出了声音的特征。&lt;/p&gt;&#10;&lt;p&gt;现在我们都知道，声音的特征就是大家熟知的『声音三要素』：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;响度：表示声音的大小。&lt;/li&gt;&#10;&lt;li&gt;音调：表示声音的高低。&lt;/li&gt;&#10;&lt;li&gt;音色：表示声音的特色。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;基于声音的特征继续研究下去，我们还可以发现与之相关的规律和因果关系，并通过一些手段来形象化的展示它们。&lt;/p&gt;&#10;&lt;p&gt;人耳能感觉到的声波频率范围在20～20000Hz，称为音频波。在这个频率范围以外的振动波，就其物理特性而言与声波相似，但对人类不引起声音感觉。声速亦称音速，是声波通过介质传播的速度，它和介质的性质与状态(如温度)等因素有关。当温度为22℃时，空气中声速为334.8m/s，水中声速为1440m/s，钢铁中声速为5000m/s。&lt;/p&gt;&#10;&lt;p&gt;对于单频率的振动，通过波形图来看声音的相关信息是很简单明了的。而实际情况中，我们听到的声音往往是复杂振动的叠加，比如下图这样：&lt;br&gt;&#10;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/boixngtu.jpeg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;对于上图这样单频率的振动，通过波形图来看声音的相关信息是很简单明了的。而实际情况中，我们听到的声音往往是复杂振动的叠加，比如下图这样：&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio_fft.jpeg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;将该复合波形进行傅里叶变换，拆解还原成每个频率上单一的正弦波构成，相当于把二维的波形图往纸面方向拉伸，变成了三维的立体模型，而拉伸方向上的那根轴叫频率，现在从小到大每个频率点上都对应着一条不同幅值和相位的正弦波。&lt;/p&gt;&#10;&lt;p&gt;下图是一个声音的波形图（上部分）和频谱图（下部分）的示例：&lt;br&gt;&#10;&lt;img src="https://cos-1251969284.cos.ap-nanjing.myqcloud.com/vedio/audio-desc.webp" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;其中，波形图比较简单，横轴是时间，纵轴是响度，并区分了左右声道。&lt;/p&gt;&#10;&lt;p&gt;但频谱图相对我们上面讲的定义要更复杂一些了，这里的频谱图是一个三维图，其中横轴是时间，纵轴是频率（这里用了音调表示，比如 A5(880) 对应的频率是 880Hz），颜色亮度表示响度。所以频谱图相对于波形图，是包含有更多信息的，唯一的缺点就是无法表示整体音量的大小，所以一般和波形配合使用来辨别声音特征。&lt;/p&gt;</description></item><item><title>FFMPEG常用命令</title><link>https://www.oneyearago.me/2023/03/26/FFMPEG_commands/</link><pubDate>Sun, 26 Mar 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/03/26/FFMPEG_commands/</guid><description>&lt;h1 id="FFMPEG常用命令"&gt;FFMPEG常用命令&lt;/h1&gt;&#10;&lt;p&gt;FFMPEG是特别强大的专门用于处理音视频的开源库。你既可以使用它的API对音视频进行处理，也可以使用它提供的工具，如 ffmpeg, ffplay, ffprobe，来编辑你的音视频文件。&lt;/p&gt;&#10;&lt;p&gt;本文将简要介绍一下 FFMPEG 库的基本目录结构及其功能，然后详细介绍一下我们在日常工作中，如何使用 ffmpeg 提供的工具来处理音视频文件。&lt;/p&gt;&#10;&lt;h1 id="FFMPEG-目录及作用"&gt;FFMPEG 目录及作用&lt;/h1&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;libavcodec： 提供了一系列编码器的实现。&lt;/li&gt;&#10;&lt;li&gt;libavformat： 实现在流协议，容器格式及其本IO访问。&lt;/li&gt;&#10;&lt;li&gt;libavutil： 包括了hash器，解码器和各利工具函数。&lt;/li&gt;&#10;&lt;li&gt;libavfilter： 提供了各种音视频过滤器。&lt;/li&gt;&#10;&lt;li&gt;libavdevice： 提供了访问捕获设备和回放设备的接口。&lt;/li&gt;&#10;&lt;li&gt;libswresample： 实现了混音和重采样。&lt;/li&gt;&#10;&lt;li&gt;libswscale： 实现了色彩转换和缩放工能。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h1 id="FFMPEG基本概念"&gt;FFMPEG基本概念&lt;/h1&gt;&#10;&lt;p&gt;在讲解 FFMPEG 命令之前，我们先要介绍一些音视频格式的基要概念。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;音／视频流&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;在音视频领域，我们把一路音／视频称为一路&lt;strong&gt;流&lt;/strong&gt;。如我们小时候经常使用VCD看港片，在里边可以选择粤语或国语声音，其实就是CD视频文件中存放了两路音频流，用户可以选择其中一路进行播放。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;容器&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;我们一般把 MP4､ FLV、MOV等文件格式称之为&lt;strong&gt;容器&lt;/strong&gt;。也就是在这些常用格式文件中，可以存放多路音视频文件。以 MP4 为例，就可以存放一路视频流，多路音频流，多路字幕流。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;channel&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;channel是音频中的概念，称之为声道。在一路音频流中，可以有单声道，双声道或立体声。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h1 id="FFMPEG-命令"&gt;FFMPEG 命令&lt;/h1&gt;&#10;&lt;p&gt;我们按使用目的可以将 FFMPEG 命令分成以下几类：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;基本信息查询命令&lt;/li&gt;&#10;&lt;li&gt;录制&lt;/li&gt;&#10;&lt;li&gt;分解/复用&lt;/li&gt;&#10;&lt;li&gt;处理原始数据&lt;/li&gt;&#10;&lt;li&gt;滤镜&lt;/li&gt;&#10;&lt;li&gt;切割与合并&lt;/li&gt;&#10;&lt;li&gt;图／视互转&lt;/li&gt;&#10;&lt;li&gt;直播相关&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;除了 FFMPEG 的基本信息查询命令外，其它命令都按下图所示的流程处理音视频。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _______ ______________&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| | | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| input | demuxer | encoded data | decoder&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| file | ---------&amp;gt; | packets | -----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|_______| |______________| |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _________&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | decoded |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | frames |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |_________|&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ________ ______________ |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| | | | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| output | &amp;lt;-------- | encoded data | &amp;lt;----+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| file | muxer | packets | encoder&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|________| |______________|&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ffmpeg调用libavformat库（包含demuxers）来读取输入文件并获取包含编码数据的数据包。 当有多个输入文件时，ffmpeg会尝试通过跟踪任何活动输入流上的最低时间戳来使其保持同步。&lt;/p&gt;</description></item><item><title>数字货币交易撮合引擎demo</title><link>https://www.oneyearago.me/2023/02/05/Design_of_Digital_Currency_Exchange_Matching_Engine/</link><pubDate>Sun, 05 Feb 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/02/05/Design_of_Digital_Currency_Exchange_Matching_Engine/</guid><description>&lt;p&gt;&lt;strong&gt;注：本文是普及金融市场交易的基础知识，不是为了鼓励大家去炒股炒币。相反，通过学习证券知识，理解衍生品原理，可以更好地认识市场风险，自觉放弃杠杆赌博暴富等不切实际的幻想，避免落入各种理财、带单、空气币等诈骗套路。本文一切内容均不构成任何投资意见或建议。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog-1251969284.cos.ap-guangzhou.myqcloud.com/web3/gupiao.png#id=pgqp2&amp;amp;originHeight=360&amp;amp;originWidth=640&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=" alt=""&gt;&lt;br&gt;&#10;在证券交易系统中，撮合引擎是实现买卖盘成交的关键组件。本文我们将分析撮合引擎的工作原理，并以最简化的Java代码设计并实现一个撮合引擎。&lt;/p&gt;&#10;&lt;p&gt;在证券市场中，撮合交易是一种微观价格发现模型，它允许买卖双方各自提交买卖订单并报价，按价格优先，时间优先的顺序，凡买单价格大于等于卖单价格时，双方即达成价格协商并成交。在A股身经百战的老股民对此规则应该非常熟悉，这里不再详述。&lt;/p&gt;&#10;&lt;p&gt;我们将讨论如何从技术上来实现它。对于撮合引擎来说，它必须维护两个买卖盘列表，一个买盘，一个卖盘，买盘按价格从高到低排序，确保报价最高的订单排在最前面；卖盘则相反，按照价格从低到高排序，确保报价最低的卖单排在最前面。&lt;/p&gt;&#10;&lt;p&gt;下图是一个实际的买卖盘：&lt;br&gt;&#10;&lt;img src="https://blog-1251969284.cos.ap-guangzhou.myqcloud.com/web3/gupiaoshendu.png#id=iSce4&amp;amp;originHeight=316&amp;amp;originWidth=147&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;对于买盘来说，上图的订单排序为2086.50，2086.09，2086.06，20860，2085.97，……&lt;/p&gt;&#10;&lt;p&gt;对于卖盘来说，上图的订单排序为2086.55，2086.75，2086.77，2086.90，2086.99，……&lt;/p&gt;&#10;&lt;p&gt;不可能出现买1价格大于等于卖1价格的情况，因为这意味着应该成交的买卖订单没有成交却在订单簿上等待成交。&lt;/p&gt;&#10;&lt;p&gt;对于多个价格相同的订单，例如2086.55，很可能张三卖出1，李四卖出3，累计数量是4。当一个新的买单价格≥2086.55时，到底优先和张三的卖单成交还是优先和李四的卖单成交呢？这要看张三和李四的订单时间谁更靠前。&lt;/p&gt;&#10;&lt;p&gt;有了以上规则，我们先设计一个最简单的订单类&lt;code&gt;Order&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public class Order {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public final long sequenceId; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public final Direction direction; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public final BigDecimal price; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public BigDecimal amount; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public Order(long sequenceId, Direction direction, BigDecimal price, BigDecimal amount) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;虽然订单的成交规则遵循价格优先、时间优先的原则，但我们并不会比较两个订单的下单时间，而是给接收到的所有订单依次赋予一个递增的序列号，序列号越小的自然时间越早，因此排在前面。时间本身实际上是订单的一个普通属性，不参与业务排序。&lt;/p&gt;&#10;&lt;p&gt;订单方向用枚举&lt;code&gt;Direction&lt;/code&gt;实现，它仅有&lt;code&gt;BUY&lt;/code&gt;和&lt;code&gt;SELL&lt;/code&gt;两个值：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public enum Direction {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; BUY, SELL;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;价格和数量以&lt;code&gt;BigDecimal&lt;/code&gt;表示。不用&lt;code&gt;float&lt;/code&gt;或&lt;code&gt;double&lt;/code&gt;的原因在于计算机无法进行精确的浮点数运算，而金融交易的价格、数量是不允许有误差的，因此，必须用&lt;code&gt;BigDecimal&lt;/code&gt;作为定点数运算。&lt;/p&gt;&#10;&lt;p&gt;下一步是实现订单簿&lt;code&gt;OrderBook&lt;/code&gt;的表示。一个直观的想法是使用&lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt;，并对订单进行排序。但是，在证券交易中，使用&lt;code&gt;List&lt;/code&gt;会导致两个致命问题：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;插入新的订单时，必须从头扫描&lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt;，以便在合适的地方插入&lt;code&gt;Order&lt;/code&gt;，平均耗时O(N)；&lt;/li&gt;&#10;&lt;li&gt;取消订单时，也必须从头扫描&lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt;，平均耗时O(N)。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;更好的方法是使用红黑树，它是一种自平衡的二叉排序树，插入和删除的效率都是O(logN)，对应的Java类是&lt;code&gt;TreeMap&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;所以我们定义&lt;code&gt;OrderBook&lt;/code&gt;的结构就是一个&lt;code&gt;TreeMap&amp;lt;OrderKey, Order&amp;gt;&lt;/code&gt;，它的排序根据&lt;code&gt;OrderKey&lt;/code&gt;决定。由业务规则可知，负责排序的&lt;code&gt;OrderKey&lt;/code&gt;只需要&lt;code&gt;sequenceId&lt;/code&gt;和&lt;code&gt;price&lt;/code&gt;即可：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public class OrderKey {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public final long sequenceId;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public final BigDecimal price;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因此，&lt;code&gt;OrderBook&lt;/code&gt;的核心数据结构就可以表示如下：&lt;/p&gt;</description></item><item><title>可变基数树技术分享</title><link>https://www.oneyearago.me/2023/01/12/principle_and_optimization_of_radix_tree/</link><pubDate>Thu, 12 Jan 2023 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/01/12/principle_and_optimization_of_radix_tree/</guid><description>&lt;h2 id="可变基数树分享"&gt;可变基数树分享&lt;/h2&gt;&#10;&lt;p&gt;使用可变基数树优化交易订单簿以及使用SIMD硬件加速查找&lt;br&gt;&#10;&lt;img src="https://blog-1251969284.cos.ap-guangzhou.myqcloud.com/radixtree/orderbook.jpeg" alt=""&gt;&lt;br&gt;&#10;主要参考论文: &lt;a href="https://db.in.tum.de/~leis/papers/ART.pdf"&gt;https://db.in.tum.de/~leis/papers/ART.pdf&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3 id="背景"&gt;背景&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;内存变得容量更大更便宜，整个数据库/存储引擎可以放到内存&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;传统的内存数据索引一般是平衡二叉树 在现代的硬件上效率不高&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;另一类索引结构，hash表 只支持单点查找，范围查找不好&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;索引效率是决定性能的关键因素&lt;/p&gt;&#10;&lt;h3 id="前言"&gt;前言&lt;/h3&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;T树和二叉树 硬件支持不好 on modern hardware architectures&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;B+ tree 缓存敏感，但是update代价比较大&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;The k-ary search tree and the Fast Architecture Sensitive Tree (FAST) 使用数据级并行性与单指令多数据 (SIMD) 指令同时执行多重比较。此外，FAST 使用的数据布局通过优化利用缓存行和 TLB 来避免缓存未命中。但两种数据结构都不能支持增量更新。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;哈希表随机分散了键，只支持点查询；另一个问题是大多数哈希表不能优雅地处理增长&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;GPU 可以实现比 CPU 更高的吞吐量。然而，使用 GPU 作为专用索引硬件还不实用，因为 GPU 的内存容量有限，与主存的通信成本很高，&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Trie&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog-1251969284.cos.ap-guangzhou.myqcloud.com/radixtree/001.png" alt=""&gt;&lt;br&gt;&#10;Trie 大多数研究都集中在索引字符串上，但我们的目标是索引其他数据类型。因此，我们更喜欢术语基数树而不是 trie，因为它强调了与基数排序算法的相似性，并强调可以索引任意数据而不仅仅是字符串。&lt;br&gt;&#10;在实际实现上，“基” 一般是 2^K中的 K，下面解释。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="基数数是什么结构？"&gt;基数数是什么结构？&lt;/h3&gt;&#10;&lt;p&gt;按字母顺序排列的字典书中的拇指索引。一个词的第一个字符可以直接用来跳到所有以该字符开始的词。在计算机中，这个过程可以用接下来的字符重复进行，直到找到一个匹配的字符。作为这个过程的结果，所有的操作都有O(k)的复杂性，其中k是key的长度。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog-1251969284.cos.ap-guangzhou.myqcloud.com/radixtree/002.png" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;特点：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;路径压缩和懒惰扩展，使ART能够通过折叠节点有效地索引长键，从而降低树的高度&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;根据子节点的数量动态地选择结构&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;树的高度与Node个数无关，与key长度有关&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;all insertion orders result in the same tree&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;key是隐藏的，可以从路径获得&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;不需要平衡&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Key按字典顺序存储&lt;/p&gt;&#10;&lt;h4 id="可伸缩的node"&gt;可伸缩的node&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://blog-1251969284.cos.ap-guangzhou.myqcloud.com/radixtree/003.png" alt=""&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>我的程序员生涯_雷军</title><link>https://www.oneyearago.me/2023/01/02/programmer_leijun/</link><pubDate>Mon, 02 Jan 2023 22:26:39 +0800</pubDate><guid>https://www.oneyearago.me/2023/01/02/programmer_leijun/</guid><description>&lt;blockquote&gt;&#10;&lt;p&gt;有的人学习编程技术，是把高级程序员做为追求的目标，甚至是终身的奋斗目标。后来参与了真正的商品化软件开发后，反而困惑了，茫然了。&lt;/p&gt;&#10;&lt;p&gt;一个人只要有韧性和灵性，有机会接触并学习电脑的编程技术，就会成为一个不错的程序员。刚开始写程序，这时候学得多的人写的好，到了后来，大家都上了一个层次，谁写的好只取决于这个人是否细心、有韧性、有灵性。掌握多一点或少一点，很快就能补上。成为一个高级程序员并不是件困难的事。&lt;/p&gt;&#10;&lt;p&gt;当我上学的时候，高级程序员也曾是我的目标，我希望我的技术能得到别人的承认。后来发现无论多么高级的程序员都没用，关键是你是否能够出想法出产品，你的劳动是否能被社会承认，能为社会创造财富。成为高级程序员绝对不是追求的目标。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;最近，和UCWEB同事讨论，怎么才能把我们的UCWEB做到极致。我说，“手机上的平台非常多，如果想做好，需要足够多、足够优秀的程序员。&lt;strong&gt;优秀的程序员如何定义呢？首先必须热爱写程序，其次必须是一个完美主义者。&lt;/strong&gt; 只有这样的人，才能把事情做得极致。”&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;说着说着，我怀念起我过去写程序的日子，从1987年到1996年，那是一段阳光灿烂的日子。几年前我无意中从网上找到了1996年在金山西点BBS上写几篇帖子，现在读来感慨万千。谢谢当年的网管Tony Low，他收藏了我的文章，使当年的文章能保留至今。&#10;&#10;重贴此文，纪念我已经远去的长达十年的程序员生涯！&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="我的程序人生路"&gt;我的程序人生路&lt;/h3&gt;&#10;&lt;p&gt;1996年5月写于金山西点BBS&lt;/p&gt;&#10;&lt;p&gt;【题记】 如果程序人生的话，这条路太漫长&lt;/p&gt;&#10;&lt;p&gt;我并非天生喜欢写程序，上高中时也没有想过程序员的生活。我学电脑非常偶然，小时好友上大学时选择了计算机系，为了和这个朋友有更多的共同语言，我也选择了计算机系，开始步入程序人生的道路。&lt;/p&gt;&#10;&lt;p&gt;当我学会一些后，发现自己特别喜欢写程序。我是八七年上的武汉大学计算机系，大一下学期才有专业课。当我有资格上机的时候，发现电脑世界太美妙，就一头扎进去。当时用的是 Motorola 68000 (相当 于 Intel 8088), 540K 的内存，运行的 UNIX 操作系统，八个人一起用。大二学PC，又过了一学期，开始出现在老师的实验室，帮忙干活，当时就写了现在很多人用的 RI （RAMinit, 清内存的小工具, 看来我还是最早一批写 Shareware 的人）。又过了一个学期，开始和校外的公司接触。大二暑假，也就是1989年8月，和一个朋友组建了 Yellow Rose 软件小组，写了我第一个商品软件 BITLOK 0.99。后来自己创业办过公司，也写过一些其他的软件。&lt;/p&gt;&#10;&lt;p&gt;大学毕业后，分到研究所，不太适应那里的气氛，就在1992年初加入金山软件，开始了职业程序员的生涯。后来成了金山软件研发部门的主管，但我一直都是一线的程序员。&lt;/p&gt;&#10;&lt;h4 id="程序员活在自己想象的王国里"&gt;程序员活在自己想象的王国里&lt;/h4&gt;&#10;&lt;p&gt;我刚接触电脑就发现电脑的妙处，电脑远没有人那么复杂。如果你的程序写得好，你就可以和电脑处好关系，就可以指挥电脑干你想干的事。这个时候你是十足的主宰。每每你坐在电脑面前，你就是在你的王国里巡行，这样的日子简直就是天堂般的日子。电脑里的世界很大，编程人是活在自己想象的王国里。你可以想象到电脑里细微到每一个字节、每一个比特的东西。&lt;/p&gt;&#10;&lt;h4 id="我爱编程这个工作，可以肯定我会干上一辈子"&gt;我爱编程这个工作，可以肯定我会干上一辈子&lt;/h4&gt;&#10;&lt;p&gt;不少人认为程序员最多干到三十五岁就可以收山换环境了，脑子也差不多该歇歇了，体力也不支了。并认为写程序是年轻人的事情，到了一定岁数，估计没什么人再当程序员了。&lt;/p&gt;&#10;&lt;p&gt;当我刚有一点本事的时候，我也和大家一样觉得编程辛苦，也想三十岁后干别的。当我年长一点后就发现了自己的无知。一个人大学毕业就二十一二岁，有点水平的时候可能二十五，接着就是过日子诸多事情。一切搞掂的时候，也许就是三十五岁。如果这样的话，我们就不用选择程序人生的道路。电脑进入中国时间并不短，但真正大规模开始用，还是八五年 PC 开始的，因此国内真正写电脑程序的人最长也就写了十几年（不知道是否还有这样的人）。由于电脑应用在国内时间比较短，国内开发的主力是三十五岁以下的年轻人为主。但这不表示程序员如同红粉佳人般的容易衰老。美国主力工程师以三十四十多岁的人为主。&lt;/p&gt;&#10;&lt;p&gt;开始的时候，我们觉得我们没有什么不能做的（现在还能听到这样的豪言壮语），而且更要命的是好象我们特别聪明，特别适合开发软件，比老外强得多。当我们真正接触那些杰出的开发人员的时候，发现他们太厉害了，都有十多年的开发经验。虽然也有很多年轻人做了很多好东西，但绝大多数的产品出自这些有丰富开发经验的程序员的手。刚毕业的时候，编程不仅仅是爱好，而且也成了一辈子的工作。整天不知道写些什么东西，觉得特别没劲，找不到感觉，特别灰心。后来，才明白，只有全身心地投入，程序才会有感觉。&lt;/p&gt;&#10;&lt;p&gt;写程序的活特别费脑子，也特别累，但我喜欢，可以肯定我会干上一辈子，虽然我没有打算一生只干这一件事。用一生来编程序是一件既容易又困难的事。如果碌碌无为，为交差写点程序，这样的日子太好混了。但如果想全身心地写程序，写十年就不是一件容易的事。现在我不少朋友都洗手了，有时我也想“用什么电脑呀，Windows 外的世界不是也很大吗？”。面对电脑的时候，立刻顿悟：写程序还是自己最擅长的事，也是最喜欢的事。&lt;/p&gt;&#10;&lt;h4 id="高级程序员不是追求的目标"&gt;高级程序员不是追求的目标&lt;/h4&gt;&#10;&lt;p&gt;有的人学习编程技术，是把高级程序员做为追求的目标，甚至是终身的奋斗目标。后来参与了真正的商品化软件开发后，反而困惑了，茫然了。&lt;/p&gt;&#10;&lt;p&gt;一个人只要有韧性和灵性，有机会接触并学习电脑的编程技术，就会成为一个不错的程序员。刚开始写程序，这时候学得多的人写的好，到了后来，大家都上了一个层次，谁写的好只取决于这个人是否细心、有韧性、有灵性。掌握多一点或少一点，很快就能补上。成为一个高级程序员并不是件困难的事。&lt;/p&gt;&#10;&lt;p&gt;当我上学的时候，高级程序员也曾是我的目标，我希望我的技术能得到别人的承认。后来发现无论多么高级的程序员都没用，关键是你是否能够出想法出产品，你的劳动是否能被社会承认，能为社会创造财富。成为高级程序员绝对不是追求的目标。&lt;/p&gt;&#10;&lt;h4 id="编程不仅仅是技术，还是艺术"&gt;编程不仅仅是技术，还是艺术&lt;/h4&gt;&#10;&lt;p&gt;有人认为编程是一种熟练工种，也有人把编程说成是艺术创作。这两种意见争论比较激烈。&lt;/p&gt;&#10;&lt;p&gt;我们换个工种来看，石匠应该是熟练工种，属于工人，更和艺术似乎沾不上边。但正是这些石匠，给我们留下多少文物古迹，如乐山大佛、莫高窟等等。应该说这些石匠给我们留下了无穷的文化财产。现代软件工业已具相当规模，很多软件的完成需要的是大兵团作战。一名普通程序员接受编写某一模块的任务后，往往只是写代码，发挥的余地很小。在大项目中，很多程序员只能了解到和自己所编模块相关的很局部的细节，另外还受到开发环境的限制，真的很难体会到自己在从事”艺术”创造，更多的时候是感到自己在从事重体力劳动。有的时候还担心自己苦苦参与的这个项目究竟有没有意义，是不是在同类产品中有竞争力，会不会开发出来以后就因为硬件的发展，操作系统的换代而过时……&lt;/p&gt;&#10;&lt;p&gt;我认为编程的工作和石匠比较相似，有技术活，更多的是体力活。不管怎么说，写出一个好软件不是一件容易的事。&lt;/p&gt;&#10;&lt;p&gt;这两种想法都有片面性，编程应该说两种属性都有。编程不仅仅是技术，也还是艺术。编程是技术活，才有可能大规模进行，才会有软件工程和软件工厂。也正是编程是艺术，才会有如此多的好产品，让大家如痴如醉。&lt;/p&gt;&#10;&lt;p&gt;— 雷军 (ID: Lei Jun) —&lt;/p&gt;&#10;&lt;p&gt;… 在风中亮出自己的旗帜&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Origin: West-Point Programmer’s BBS&lt;/li&gt;&#10;&lt;li&gt;86-10-6237-8307 (6:650/27)&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>Go语言 goroutine调度</title><link>https://www.oneyearago.me/2022/12/16/go_sched_02/</link><pubDate>Fri, 16 Dec 2022 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2022/12/16/go_sched_02/</guid><description>&lt;p&gt;G、P、M 是 Go 调度器的三个核心组件，各司其职。在它们精密地配合下，Go 调度器得以高效运转，这也是 Go 天然支持高并发的内在动力。今天这篇文章我们来深入理解 GPM 模型。&lt;/p&gt;&#10;&lt;p&gt;先看 G，取 goroutine 的首字母，主要保存 goroutine 的一些状态信息以及 CPU 的一些寄存器的值，例如 IP 寄存器，以便在轮到本 goroutine 执行时，CPU 知道要从哪一条指令处开始执行。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;当 goroutine 被调离 CPU 时，调度器负责把 CPU 寄存器的值保存在 g 对象的成员变量之中。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;当 goroutine 被调度起来运行时，调度器又负责把 g 对象的成员变量所保存的寄存器值恢复到 CPU 的寄存器。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;本系列使用的代码版本是 1.9.2，来看一下 g 的源码：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// goroutine 使用的栈&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// offset known to runtime/cgo&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 用于栈的扩张和收缩检查，抢占标志&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;stackguard0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// offset known to liblink&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;stackguard1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// offset known to liblink&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;_panic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;_panic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// innermost panic - offset known to liblink&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;_defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;_defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// innermost defer&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 当前与 g 绑定的 m&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// current m; offset known to arm liblink&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// goroutine 的运行现场&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sched&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gobuf&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;syscallsp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// if status==Gsyscall, syscallsp = sched.sp to use during gc&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;syscallpc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// if status==Gsyscall, syscallpc = sched.pc to use during gc&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;stktopsp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// expected sp at top of stack, to check in traceback&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// wakeup 时传入的参数&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// passed parameter on wakeup&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;atomicstatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;stackLock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// sigprof/scang lock; TODO: fold in to atomicstatus&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;goid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// g 被阻塞之后的近似时间&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;waitsince&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// approx time when the g become blocked&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// g 被阻塞的原因&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;waitreason&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// if status==Gwaiting&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 指向全局队列里下一个 g&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;schedlink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;guintptr&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 抢占调度标志。这个为 true 时，stackguard0 等于 stackpreempt&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;preempt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// preemption signal, duplicates stackguard0 = stackpreempt&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;paniconfault&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// panic (instead of crash) on unexpected fault address&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;preemptscan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// preempted g does scan for gc&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gcscandone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// g has scanned stack; protected by _Gscan bit in status&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gcscanvalid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// false at start of gc cycle, true if G has not run since last scan; TODO: remove?&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;throwsplit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// must not split stack&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;raceignore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ignore race detection events&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sysblocktraced&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// StartTrace has emitted EvGoInSyscall about this goroutine&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// syscall 返回之后的 cputicks，用来做 tracing&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sysexitticks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// cputicks when syscall has returned (for tracing)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;traceseq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// trace event sequencer&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;tracelastp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;puintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// last P emitted an event for this goroutine&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 如果调用了 LockOsThread，那么这个 g 会绑定到某个 m 上&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;lockedm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;writebuf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sigcode0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sigcode1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sigpc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 创建该 goroutine 的语句的指令地址&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gopc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// pc of go statement that created this goroutine&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// goroutine 函数的指令地址&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;startpc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// pc of goroutine function&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;racectx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;waiting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;sudog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// sudog structures this g is waiting on (that have a valid elem ptr); in lock order&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;cgoCtxt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// cgo traceback context&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// profiler labels&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// time.Sleep 缓存的定时器&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// cached timer for time.Sleep&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gcAssistBytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;源码中，比较重要的字段我已经作了注释，其他未作注释的与调度关系不大或者我暂时也没有理解的。&lt;/p&gt;</description></item><item><title>Go语言 goroutine VS thread</title><link>https://www.oneyearago.me/2022/11/16/go_sched_01/</link><pubDate>Wed, 16 Nov 2022 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2022/11/16/go_sched_01/</guid><description>&lt;p&gt;goroutine，绕不开的一个话题是：它和 thread 有什么区别？&lt;/p&gt;&#10;&lt;p&gt;参考资料【How Goroutines Work】告诉我们可以从三个角度区别：内存消耗、创建与销毀、切换。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;内存占用&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;创建一个 goroutine 的栈内存消耗为 2 KB，实际运行过程中，如果栈空间不够用，会自动进行扩容。创建一个 thread 则需要消耗 1 MB 栈内存，而且还需要一个被称为 “a guard page” 的区域用于和其他 thread 的栈空间进行隔离。&lt;/p&gt;&#10;&lt;p&gt;对于一个用 Go 构建的 HTTP Server 而言，对到来的每个请求，创建一个 goroutine 用来处理是非常轻松的一件事。而如果用一个使用线程作为并发原语的语言构建的服务，例如 Java 来说，每个请求对应一个线程则太浪费资源了，很快就会出 OOM 错误（OutOfMemoryError）。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;创建和销毀&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Thread 创建和销毀都会有巨大的消耗，因为要和操作系统打交道，是内核级的，通常解决的办法就是线程池。而 goroutine 因为是由 Go runtime 负责管理的，创建和销毁的消耗非常小，是用户级。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;切换&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;当 threads 切换时，需要保存各种寄存器，以便将来恢复：&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;16 general purpose registers, PC (Program Counter), SP (Stack Pointer), segment registers, 16 XMM registers, FP coprocessor state, 16 AVX registers, all MSRs etc.&lt;/p&gt;&#10;&lt;/blockquote&gt;</description></item><item><title>Go语言 - 从 channel 接收数据</title><link>https://www.oneyearago.me/2022/11/11/go_chan_03/</link><pubDate>Fri, 11 Nov 2022 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2022/11/11/go_chan_03/</guid><description>&lt;h1 id="源码分析"&gt;源码分析&lt;/h1&gt;&#10;&lt;p&gt;我们先来看一下接收相关的源码。在清楚了接收的具体过程之后，再根据一个实际的例子来具体研究。&lt;/p&gt;&#10;&lt;p&gt;接收操作有两种写法，一种带 “ok”，反应 channel 是否关闭；一种不带 “ok”，这种写法，当接收到相应类型的零值时无法知道是真实的发送者发送过来的值，还是 channel 被关闭后，返回给接收者的默认类型的零值。两种写法，都有各自的应用场景。&lt;/p&gt;&#10;&lt;p&gt;经过编译器的处理后，这两种写法最后对应源码里的这两个函数：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// entry points for &amp;lt;- c from compiled code&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;chanrecv1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;hchan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nf"&gt;chanrecv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;chanrecv2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;hchan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;chanrecv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;chanrecv1&lt;/code&gt; 函数处理不带 “ok” 的情形，&lt;code&gt;chanrecv2&lt;/code&gt; 则通过返回 “received” 这个字段来反应 channel 是否被关闭。接收值则比较特殊，会“放到”参数 &lt;code&gt;elem&lt;/code&gt; 所指向的地址了，这很像 C/C++ 里的写法。如果代码里忽略了接收值，这里的 elem 为 nil。&lt;/p&gt;&#10;&lt;p&gt;无论如何，最终转向了 &lt;code&gt;chanrecv&lt;/code&gt; 函数：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 位于 src/runtime/chan.go&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// chanrecv 函数接收 channel c 的元素并将其写入 ep 所指向的内存地址。&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 如果 ep 是 nil，说明忽略了接收值。&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 如果 block == false，即非阻塞型接收，在没有数据可接收的情况下，返回 (false, false)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 否则，如果 c 处于关闭状态，将 ep 指向的地址清零，返回 (true, false)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 否则，用返回值填充 ep 指向的内存地址。返回 (true, true)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 如果 ep 非空，则应该指向堆或者函数调用者的栈&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;chanrecv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;hchan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 省略 debug 内容 …………&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 如果是一个 nil 的 channel&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 如果不阻塞，直接返回 (false, false)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 否则，接收一个 nil 的 channel，goroutine 挂起&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;gopark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;chan receive (nil chan)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;traceEvGoStop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 不会执行到这里&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;unreachable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 在非阻塞模式下，快速检测到失败，不用获取锁，快速返回&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 当我们观察到 channel 没准备好接收：&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 1. 非缓冲型，等待发送列队 sendq 里没有 goroutine 在等待&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 2. 缓冲型，但 buf 里没有元素&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 之后，又观察到 closed == 0，即 channel 未关闭。&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 因为 channel 不可能被重复打开，所以前一个观测的时候 channel 也是未关闭的，&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 因此在这种情况下可以直接宣布接收失败，返回 (false, false)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Loaduint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qcount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;blockprofilerate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cputicks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 加锁&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// channel 已关闭，并且循环数组 buf 里没有元素&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 这里可以处理非缓冲型关闭 和 缓冲型关闭但 buf 无元素的情况&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 也就是说即使是关闭状态，但在缓冲型的 channel，&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// buf 里有元素的情况下还能接收到元素&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qcount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;raceenabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;raceacquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 解锁&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 从一个已关闭的 channel 执行接收操作，且未忽略返回值&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 那么接收的值将是一个该类型的零值&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// typedmemclr 根据类型清理相应地址的内存&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;typedmemclr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elemtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 从一个已关闭的 channel 接收，selected 会返回true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 等待发送队列里有 goroutine 存在，说明 buf 是满的&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 这有可能是：&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 1. 非缓冲型的 channel&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 2. 缓冲型的 channel，但 buf 满了&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 针对 1，直接进行内存拷贝（从 sender goroutine -&amp;gt; receiver goroutine）&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 针对 2，接收到循环数组头部的元素，并将发送者的元素放到循环数组尾部&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// Found a waiting sender. If buffer is size 0, receive value&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// directly from sender. Otherwise, receive from head of queue&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// and add sender&amp;#39;s value to the tail of the queue (both map to&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// the same buffer slot because the queue is full).&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 缓冲型，buf 里有元素，可以正常接收&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qcount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 直接从循环数组里找到要接收的元素&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;qp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;chanbuf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recvx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// …………&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 代码里，没有忽略要接收的值，不是 &amp;#34;&amp;lt;- ch&amp;#34;，而是 &amp;#34;val &amp;lt;- ch&amp;#34;，ep 指向 val&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;typedmemmove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elemtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 清理掉循环数组里相应位置的值&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;typedmemclr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elemtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 接收游标向前移动&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recvx&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 接收游标归零&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recvx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recvx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// buf 数组里的元素个数减 1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qcount&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 解锁&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 非阻塞接收，解锁。selected 返回 false，因为没有接收到值&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 接下来就是要被阻塞的情况了&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 构造一个 sudog&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;acquireSudog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;releasetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;releasetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 待接收数据的地址保存下来&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitlink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectdone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 进入channel 的等待接收队列&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recvq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 将当前 goroutine 挂起&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nf"&gt;goparkunlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;chan receive&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;traceEvGoBlockRecv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 被唤醒了，接着从这里继续执行一些扫尾工作&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;G waiting list is corrupted&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;releasetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;blockevent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;releasetime&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nf"&gt;releaseSudog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面的代码注释地比较详细了，你可以对着源码一行行地去看，我们再来详细看一下。&lt;/p&gt;</description></item><item><title>Go语言 - 向 channel 发送数据</title><link>https://www.oneyearago.me/2022/11/10/go_chan_02/</link><pubDate>Thu, 10 Nov 2022 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2022/11/10/go_chan_02/</guid><description>&lt;h1 id="源码分析"&gt;源码分析&lt;/h1&gt;&#10;&lt;p&gt;发送操作最终转化为 &lt;code&gt;chansend&lt;/code&gt; 函数，直接上源码，同样大部分都注释了，可以看懂主流程：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 位于 src/runtime/chan.go&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;chansend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;hchan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;callerpc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 如果 channel 是 nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 不能阻塞，直接返回 false，表示未发送成功&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 当前 goroutine 被挂起&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;gopark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;chan send (nil chan)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;traceEvGoStop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;unreachable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 省略 debug 相关……&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 对于不阻塞的 send，快速检测失败场景&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;//&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 如果 channel 未关闭且 channel 没有多余的缓冲空间。这可能是：&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 1. channel 是非缓冲型的，且等待接收队列里没有 goroutine&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 2. channel 是缓冲型的，但循环数组已经装满了元素&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recvq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qcount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;blockprofilerate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cputicks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 锁住 channel，并发安全&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 如果 channel 关闭了&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 解锁&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 直接 panic&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plainError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;send on closed channel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 如果接收队列里有 goroutine，直接将要发送的数据拷贝到接收 goroutine&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recvq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 对于缓冲型的 channel，如果还有缓冲空间&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qcount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// qp 指向 buf 的 sendx 位置&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;qp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;chanbuf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// ……&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 将数据从 ep 处拷贝到 qp&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;typedmemmove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elemtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;qp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 发送游标值加 1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendx&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 如果发送游标值等于容量值，游标值归 0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 缓冲区的元素数量加一&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;qcount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 解锁&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 如果不需要阻塞，则直接返回错误&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;block&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// channel 满了，发送方会被阻塞。接下来会构造一个 sudog&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 获取当前 goroutine 的指针&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getg&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;acquireSudog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;releasetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;releasetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ep&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitlink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selectdone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 当前 goroutine 进入发送等待队列&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 当前 goroutine 被挂起&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nf"&gt;goparkunlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;chan send&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;traceEvGoBlockSend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 从这里开始被唤醒了（channel 有机会可以发送了）&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;G waiting list is corrupted&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waiting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;chansend: spurious wakeup&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 被唤醒后，channel 关闭了。坑爹啊，panic&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;plainError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;send on closed channel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;gp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;releasetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&#9;&lt;/span&gt;&lt;span class="nf"&gt;blockevent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;releasetime&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 去掉 mysg 上绑定的 channel&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nf"&gt;releaseSudog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mysg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面的代码注释地比较详细了，我们来详细看看。&lt;/p&gt;</description></item><item><title>Go语言 channel底层结构</title><link>https://www.oneyearago.me/2022/11/06/go_chan_01/</link><pubDate>Sun, 06 Nov 2022 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2022/11/06/go_chan_01/</guid><description>&lt;h1 id="数据结构"&gt;数据结构&lt;/h1&gt;&#10;&lt;p&gt;底层数据结构需要看源码，版本为 go 1.9.2：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hchan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// chan 里元素数量&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;qcount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// chan 底层循环数组的长度&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;dataqsiz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 指向底层循环数组的指针&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 只针对有缓冲的 channel&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// chan 中元素大小&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;elemsize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint16&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// chan 是否被关闭的标志&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;closed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint32&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// chan 中元素类型&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;elemtype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// element type&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 已发送元素在循环数组中的索引&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sendx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// send index&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 已接收元素在循环数组中的索引&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;recvx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// receive index&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 等待接收的 goroutine 队列&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;recvq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;waitq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// list of recv waiters&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 等待发送的 goroutine 队列&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;sendq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;waitq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// list of send waiters&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="c1"&gt;// 保护 hchan 中所有字段&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#9;&lt;/span&gt;&lt;span class="nx"&gt;lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mutex&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关于字段的含义都写在注释里了，再来重点说几个字段：&lt;/p&gt;</description></item><item><title>C++内存问题排错小工具</title><link>https://www.oneyearago.me/2022/09/10/cpp_fsanitize/</link><pubDate>Sat, 10 Sep 2022 21:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2022/09/10/cpp_fsanitize/</guid><description>&lt;p&gt;在 C++ 开发中，使用 &lt;code&gt;-fsanitize=address&lt;/code&gt; 编译器标志启用了 AddressSanitizer，这是一个功能强大的内存错误检测工具，用于检测各种内存相关的错误，包括但不限于内存泄漏、越界访问和使用后释放。这个工具是为了帮助开发者在开发阶段发现潜在的错误，以增强程序的稳定性和安全性。&lt;/p&gt;&#10;&lt;h3 id="AddressSanitizer-的主要功能"&gt;AddressSanitizer 的主要功能&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;内存访问错误检测&lt;/strong&gt;：检测堆、栈以及全局变量的越界读写。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;使用后释放错误&lt;/strong&gt;：检测对已释放内存的访问。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;内存泄漏检测&lt;/strong&gt;：可以选用 LeakSanitizer 来检测内存泄漏。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;双重释放检测&lt;/strong&gt;：检测多次释放同一内存块的错误。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="fsanitize-address-的影响"&gt;&lt;code&gt;-fsanitize=address&lt;/code&gt; 的影响&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;性能影响&lt;/strong&gt;： 使用 AddressSanitizer 会对程序性能产生影响，因为它在运行时进行额外的检查。性能开销通常在2倍至3倍之间，具体取决于程序的行为和内存使用模式。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;内存使用&lt;/strong&gt;： AddressSanitizer 也会增加程序的内存使用，大约需要额外的1.5至3倍内存，这是因为它需要额外的内存来存储各种运行时数据和监控堆栈。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;诊断信息&lt;/strong&gt;： 当检测到错误时，AddressSanitizer 会提供详细的错误报告，包括错误类型、位置以及相关的内存访问信息。这些信息对于快速定位和修复错误非常有帮助。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;编译器和平台支持&lt;/strong&gt;： AddressSanitizer 支持多个主流编译器和平台，包括 GCC 和 Clang。但是，它可能不在所有编译器或平台上都提供同样的特性或性能。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="使用场景"&gt;使用场景&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;开发和调试阶段&lt;/strong&gt;：由于其对性能和内存的影响，通常推荐在开发和调试阶段使用 AddressSanitizer，而不是在生产环境中。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;自动化测试&lt;/strong&gt;：可以在自动化测试中启用 AddressSanitizer，以便在集成测试或回归测试期间捕获错误。&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>数字货币交易系统设计与开发</title><link>https://www.oneyearago.me/2022/06/23/contract_transaction_system/</link><pubDate>Thu, 23 Jun 2022 18:20:39 +0800</pubDate><guid>https://www.oneyearago.me/2022/06/23/contract_transaction_system/</guid><description>&lt;p&gt;&lt;strong&gt;注：本文是普及金融市场交易的基础知识，不是为了鼓励大家去炒股炒币。相反，通过学习证券知识，理解衍生品原理，可以更好地认识市场风险，自觉放弃杠杆赌博暴富等不切实际的幻想，避免落入各种理财、带单、空气币等诈骗套路。本文一切内容均不构成任何投资意见或建议。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h3 id="期货合约"&gt;期货合约&lt;/h3&gt;&#10;&lt;p&gt;合约交易，通常指期货合约。现货合约我们以后再讨论。这里我们仍然以数字货币的期货合约为例，实现一个基于BTC/USD价格指数的期货合约。&lt;/p&gt;&#10;&lt;p&gt;所谓期货交易，就是指以约定的价格在未来进行交割。&lt;/p&gt;&#10;&lt;p&gt;期货交易的目的原本是以当前约定的价格锁定未来某个时间段的价格，这样企业生产就可以合理地锁定采购成本，避免了价格涨跌带来的经营风险。&lt;/p&gt;&#10;&lt;p&gt;但是市场的风险并不会平白消失。风险其实并没有消失，而是转移到了市场的其他参与方。企业通过期货市场锁定现货价格，现货价格的波动风险实际上转移到了合约的对手方。因此，期货市场是一个高度投机的市场。期货市场的高收益源于交易方承担了高风险，而高风险也会带来巨额亏损。&lt;/p&gt;&#10;&lt;p&gt;期货合约与现货交易的区别是，期货合约需要交易双方以约定的“份数”进行买卖，例如黄金期货，外盘通常以“盎司”为单位进行买卖，只能买卖整数倍的合约。&lt;/p&gt;&#10;&lt;p&gt;在期货交易中，通常不需要支付全额费用，而只需要支付一定比例的保证金。根据保证金比例的不同，期货交易的杠杆也不同。例如，10%的保证金率就是10倍杠杆，5%的保证金率就是20倍杠杆。杠杆既能放大收益，也能放大亏损。&lt;/p&gt;&#10;&lt;p&gt;此外，如果不计算交易手续费，期货合约的买卖双方就是一个零和博弈，即所有交易方的盈利和亏损相加为零。&lt;/p&gt;&#10;&lt;h3 id="期货合约交易系统"&gt;期货合约交易系统&lt;/h3&gt;&#10;&lt;p&gt;根据期货合约的特点，我们就可以设计一个期货合约交易系统的所有模块：&lt;br&gt;&#10;&lt;img src="https://blog-1251969284.cos.ap-guangzhou.myqcloud.com/web3/web3-trade-system.png#id=Gr4L8&amp;amp;originHeight=520&amp;amp;originWidth=1570&amp;amp;originalType=binary&amp;amp;ratio=1&amp;amp;rotation=0&amp;amp;showTitle=false&amp;amp;status=done&amp;amp;style=none&amp;amp;title=" alt=""&gt;&lt;br&gt;&#10;当系统收到一个用户的订单请求时，首先，仍然由用户系统（User）进行身份认证，然后，确定当前交易的合约是否在有效期内，紧接着，由账户系统（Account）确定是否有足够的余额作为保证金进行交易。如果由足够的保证金，该订单请求就由订单系统（Order）创建成功，并进入定序系统（Sequence）。定序后进入撮合引擎（Match），如果成交，订单由清算系统（Clearing）进行清算。&lt;/p&gt;&#10;&lt;p&gt;和现货交易不同的是，用户订单成交后，同时创建（或者平掉）了一个仓位（Position），所以，期货合约系统需要一个仓位管理模块（Position）来管理所有用户的仓位。&lt;/p&gt;&#10;&lt;h3 id="爆仓"&gt;爆仓&lt;/h3&gt;&#10;&lt;p&gt;由于市场价格在实时变动，对于普通的期货交易所，通常是每日结算，收盘后要求亏损的用户补足保证金。而对于24小时交易的数字货币交易所，无法每日结算，因此需要一个爆仓引擎（Liquidation）。&lt;/p&gt;&#10;&lt;p&gt;爆仓引擎根据当前市场价格实时计算所有用户的仓位权益是否降至零。如果用户的仓位保证金不足，爆仓引擎就启动爆仓流程：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;检测用户是否设置了“自动转入保证金”，并且账户有足够余额；&lt;/li&gt;&#10;&lt;li&gt;如果能自动转入保证金，则自动转入，权益提高，不需要爆仓；&lt;/li&gt;&#10;&lt;li&gt;如果不能自动转入，则爆仓引擎首先接管仓位，用户对此仓位的权益归零；&lt;/li&gt;&#10;&lt;li&gt;爆仓引擎计算用户权益为零的价格，并按照该价格平仓。平仓是否成功取决于市场波动性和流动性。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;如果市场缺乏足够的流动性，导致爆仓引擎所持有仓位无法及时平仓，交易所本身就会承受损失。在某个期货合约周期内，交易所可以选择所有盈利用户共同分担损失，也可以优先选择高风险盈利用户强制减仓来提高流动性。&lt;/p&gt;&#10;&lt;p&gt;如果选择强制减仓，系统还需要一个自动减仓系统（ADL：Auto-Deleveraging），自动减仓系统根据用户风险和盈利高低对盈利仓位排序，排在靠前的仓位更有可能被强制减仓。&lt;/p&gt;&#10;&lt;p&gt;合约到期后，由清算系统（Clearing）对所有仓位进行交割，按照盈亏更新账户（Account），然后平掉所有仓位，合约终止。&lt;/p&gt;</description></item><item><title>C++程序内存泄露工具</title><link>https://www.oneyearago.me/2022/05/02/heap_profiler/</link><pubDate>Mon, 02 May 2022 21:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2022/05/02/heap_profiler/</guid><description>&lt;p&gt;在使用 gperftools 的 heap profiler 分析 C++ 程序的内存使用情况时，您可以生成多个内存分配描述文件（heap profiles），这些文件记录了程序在特定时间点的内存分配状态。通过对比这些描述文件（即进行 “diff” 操作），可以发现在两个时间点之间的内存分配差异。&lt;/p&gt;&#10;&lt;p&gt;具体来说，&lt;code&gt;pprof&lt;/code&gt; 工具可以用来查看和分析这些描述文件。通过 &lt;code&gt;pprof&lt;/code&gt; 的 &lt;code&gt;--text&lt;/code&gt;、&lt;code&gt;--list&lt;/code&gt; 和其他命令行选项，可以输出具体的内存分配详情，包括具体的函数、调用栈以及分配的内存量等信息。&lt;/p&gt;&#10;&lt;h2 id="详细步骤"&gt;详细步骤&lt;/h2&gt;&#10;&lt;h3 id="1-启动内存分析"&gt;1. 启动内存分析&lt;/h3&gt;&#10;&lt;p&gt;首先，在程序中启动 heap profiler。这通常通过设置环境变量 &lt;code&gt;HEAPPROFILE&lt;/code&gt; 来实现，这个环境变量指定了描述文件的输出路径和前缀。&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;HEAPPROFILE=/tmp/my_program_heap&#10;export HEAPPROFILE&#10;./my_program # 运行你的程序&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2-生成内存描述文件"&gt;2. 生成内存描述文件&lt;/h3&gt;&#10;&lt;p&gt;程序运行过程中，heap profiler 将定期生成内存描述文件（例如 &lt;code&gt;my_program_heap.0001.heap&lt;/code&gt;、&lt;code&gt;my_program_heap.0002.heap&lt;/code&gt; 等），每个文件都代表一个内存分配的快照。&lt;/p&gt;&#10;&lt;h3 id="3-分析和对比描述文件"&gt;3. 分析和对比描述文件&lt;/h3&gt;&#10;&lt;p&gt;使用 &lt;code&gt;pprof&lt;/code&gt; 工具来分析和对比这些文件。例如，要查看两个时间点之间的差异，可以使用以下命令：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;pprof --text --base=my_program_heap.0001.heap my_program my_program_heap.0002.heap&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="4-理解输出"&gt;4. 理解输出&lt;/h3&gt;&#10;&lt;p&gt;&lt;code&gt;pprof&lt;/code&gt; 的输出会列出内存分配的详细信息，包括增加或减少的内存分配。如果使用 &lt;code&gt;--list=&amp;lt;function_name&amp;gt;&lt;/code&gt; 选项，可以看到某个特定函数内的详细内存分配情况。&lt;/p&gt;&#10;&lt;h2 id="原理"&gt;原理&lt;/h2&gt;&#10;&lt;h3 id="原理和操作的底层函数"&gt;原理和操作的底层函数&lt;/h3&gt;&#10;&lt;p&gt;拦截内存分配函数：&lt;br&gt;&#10;Heap profiler 通过覆盖标准的内存分配和释放函数来工作，如 malloc, free, new, delete 等。当程序调用这些函数时，它实际上是调用了 profiler 提供的替代版本。&lt;/p&gt;&#10;&lt;h3 id="记录内存活动："&gt;记录内存活动：&lt;/h3&gt;&#10;&lt;p&gt;每次内存分配或释放时，heap profiler 记录关键信息，包括分配的大小、时间戳和调用栈。这些数据被用来跟踪内存使用的历史和发展趋势。&lt;/p&gt;&#10;&lt;h3 id="生成堆描述文件："&gt;生成堆描述文件：&lt;/h3&gt;&#10;&lt;p&gt;分配的数据被定期写入到堆描述文件（heap profiles），这些文件反映了程序在特定时间点的内存使用情况。这些描述文件可以用于进一步的分析，如比较两个时间点的内存使用差异。&lt;/p&gt;</description></item><item><title>基于protobuf 的 rpc</title><link>https://www.oneyearago.me/2021/07/25/protobuf-rpc/</link><pubDate>Sun, 25 Jul 2021 21:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/07/25/protobuf-rpc/</guid><description>&lt;h3 id="基于protobuf-的-rpc"&gt;基于protobuf 的 rpc&lt;/h3&gt;&#10;&lt;p&gt;protobuf 实现了序列化部分，预留了 RPC 接口，但是没有实现网络交互的部分。&lt;br&gt;&#10;基于pb里面的 service 接口，自己实现实际的通信过程，实现一个简易的 rpc是比较容易的， 对我们阅读 brpc、muduo、grpc等著名开源的rpc有很大帮助。&lt;/p&gt;&#10;&lt;p&gt;google的 文档这里描述的也比较清楚， 在 google/protobuf/service.h 里，通过一个简单的例子，描述了实现 一个基于pb的RPC的过程。&lt;/p&gt;&#10;&lt;h4 id="protoc-自动生成的代码"&gt;protoc 自动生成的代码&lt;/h4&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;When you use the protocol compiler to compile a service definition, it generates two classes: An abstract interface for the service (with methods matching the service definition) and a “stub” implementation.&lt;br&gt;&#10;A stub is just a type-safe wrapper around an RpcChannel which emulates a local implementation of the service.&lt;/p&gt;&#10;&lt;/blockquote&gt;</description></item><item><title>汇率、资产与货币</title><link>https://www.oneyearago.me/2021/07/08/money-and-assets/</link><pubDate>Thu, 08 Jul 2021 11:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/07/08/money-and-assets/</guid><description>&lt;h3 id="汇率跟什么有因素关系"&gt;汇率跟什么有因素关系&lt;/h3&gt;&#10;&lt;h4 id="1-经济增速跟汇率相关吗？"&gt;1. 经济增速跟汇率相关吗？&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;顺周期货币：经济增速与汇率正相关的国家，外围地区国家：出售原材料的发展中国家&lt;/li&gt;&#10;&lt;li&gt;逆周期货币：经济增速与汇率负相关的国家，中心地区国家：日元、瑞士法郎、美元&lt;/li&gt;&#10;&lt;li&gt;人民币：从前是顺周期，过去20年表现为无周期，现在表现为逆周期&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="关于顺周期与逆周期："&gt;关于顺周期与逆周期：&lt;/h5&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;在经济形势好的时候，利差交易，即借入利率较低的货币，然后买入并持有利率较高的货币，借此赚取利差（顺周期，外围发展快，回报高）&lt;/li&gt;&#10;&lt;li&gt;在经济出现危机时，资本从外围逃向中心&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/money/country_eco.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="2-地方政府的债务问题，债务高，汇率就会低吗？"&gt;2. 地方政府的债务问题，债务高，汇率就会低吗？&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/money/country_zhaiwu.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="债务与利率之间略有负相关"&gt;债务与利率之间略有负相关&lt;/h5&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;只有有钱的人才能借到更多的钱&lt;/li&gt;&#10;&lt;li&gt;关键的是债务的定价货币，只要用本币借的债，本质上就不是债，而是向持有这种货币的人征收的一种隐性税收&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="3-产业转移资本外流对汇率的影响"&gt;3.产业转移资本外流对汇率的影响&lt;/h4&gt;&#10;&lt;p&gt;资本外流并不会造成本币贬值，当一个国家变成资本输出方时，这个国家的货币就更具有稳定性和你周期性、&lt;/p&gt;&#10;&lt;h4 id="4-M2-GDP对汇率的影响"&gt;4.M2/GDP对汇率的影响&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;评估美国的融资投资风险应该用M3，最广义的货币供应量&lt;/li&gt;&#10;&lt;li&gt;中国主要靠银行信贷，所以靠M2&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;2008年金融危机，中心央行发行大量货币，并没有造成通胀。关键在于老龄化和工业化社会，过剩的流动性不会聚集在商品上，而是流向资产和全球外围地区&lt;/p&gt;&#10;&lt;h4 id="5-与什么有关"&gt;5. 与什么有关&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;经济增长率&lt;/li&gt;&#10;&lt;li&gt;可贸易品的价格水平&lt;/li&gt;&#10;&lt;li&gt;技术进步&lt;/li&gt;&#10;&lt;li&gt;人口结构&lt;/li&gt;&#10;&lt;li&gt;贸易开放度&lt;/li&gt;&#10;&lt;li&gt;劳动要素&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="中国被动成为最大外汇储备国"&gt;中国被动成为最大外汇储备国&lt;/h3&gt;&#10;&lt;h4 id="巨额的外汇储备不是大国的标志，而是附庸国的象征"&gt;巨额的外汇储备不是大国的标志，而是附庸国的象征&lt;/h4&gt;&#10;&lt;h4 id="中国的外汇储备是如何形成的"&gt;中国的外汇储备是如何形成的&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;中国为了鼓励工业化，进行了大规模的招商引资和鼓励出口的重商主义（1998-2012）&lt;/li&gt;&#10;&lt;li&gt;外资进入中国，需要将自己带来的外币，从所投资地区的开户行兑换成人民币&lt;/li&gt;&#10;&lt;li&gt;银行是不允许保留大量的外币的（银行外汇头寸管理制度），需要当天把多出限额的外汇，在银行间外汇市场上卖掉&lt;/li&gt;&#10;&lt;li&gt;全国各地的银行在全国范围的招商引资的情况下都在大量出售外汇。会出现一种压力，对人民币需求增加，美元供给增加。人民币兑美元出现上涨压力，这会损伤简单制造业的出口。&lt;/li&gt;&#10;&lt;li&gt;为了稳定汇率，人民银行就在外汇市场上大量印刷人民币（外汇占款），买入外汇。最高峰达到了4.2万亿美元。 由国家外汇管理局管理。&lt;/li&gt;&#10;&lt;li&gt;外管局将这些钱投资到各国的国债市场上&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="作用"&gt;作用&lt;/h5&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;2005年以前，汇率为出口导向型工业化提供支持&lt;/li&gt;&#10;&lt;li&gt;中国通过使人民币贬值，快速降低中国劳动要素的相对价格&lt;/li&gt;&#10;&lt;li&gt;抢了亚洲四小虎的制造业市场&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="弊端"&gt;弊端&lt;/h5&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;归根结底都是中国的贷款企业和存款人分担了成本&lt;/li&gt;&#10;&lt;li&gt;中国的大量外汇储备正在被量化宽松政策稀释&lt;/li&gt;&#10;&lt;li&gt;刺激了出口，沿海出口城市发展迅速，内地衰落，加速贫富分化&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="人民币升值的外部和内部压力"&gt;人民币升值的外部和内部压力&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;2008年，美国施加压力，主动要求人民币升值，认为此前人民币别低估了27.5%&lt;/li&gt;&#10;&lt;li&gt;随着对外经济的发展高峰，人民银行不得不不断买入越来越多的外汇，外汇占款越来越多，对冲这些外汇占款的成本也越来越高，通胀的压力越来越大&lt;/li&gt;&#10;&lt;li&gt;出口制造业部门拒绝人民币升值。（船舶协会：如果敢让人民币升值10%，就等着有2000万人失业）&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="稳定升值"&gt;稳定升值&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/money/renminbi_shengzhi.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;在海外获得大量低息美元/外币，以投资名字进入中国获得人民币，以此获得息差。（香港借贷利率3%，深圳存款利率4%，再加上一年5-6%的汇率升值，一年可以达到7%的收益，此外还可以每3天加一次杠杆，一年可以加70倍杠杆，一年下来利润率最高可达490%）&lt;/li&gt;&#10;&lt;li&gt;这种暴利导致这几年间大量热钱涌入中国内地。这条光滑的曲线意味着在邀请全球的投机者来赌人民币升值。但因中国资本管制，这些投机者往往以FDI产业投资或贸易的名义。这导致中国的贸易顺差和直接投资被夸大。&lt;/li&gt;&#10;&lt;li&gt;这种热钱涌入，形成了一定的泡沫。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="铸币税与财富再分配"&gt;铸币税与财富再分配&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/money/china-m2.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;勤俭节约的人实际上扮演了一个主动多交税的角色。储蓄越多，向货币体系缴纳的税就越多&lt;/li&gt;&#10;&lt;li&gt;央行的做法，实际上是为了帮助出口部门，剥夺了千万中国的储蓄者，这是强大的财富再分配。&lt;/li&gt;&#10;&lt;li&gt;一辈子辛勤劳动攒钱，将会一直处在社会底层&lt;br&gt;&#10;举例： 1980年代，假设1000元占M2是1亿分之一；30年后的今天，1000元占M2只有275亿分之一，相当于只剩下千分之3.64，它的相对购买力的99.6%已经蒸发掉了。&lt;/li&gt;&#10;&lt;li&gt;中国的基础消费品涨价并不快，是因为快速增加的生产能力&lt;br&gt;&#10;（解释为什么有了拼多多）&lt;/li&gt;&#10;&lt;li&gt;如果能够贷款赚钱，即使是8%的利率，但面对21.6%的货币扩张速度，财富增加速度大概率会比贷款利率高&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="个人如此，国家亦如此"&gt;个人如此，国家亦如此&lt;/h4&gt;&#10;&lt;p&gt;中国人积累的外汇购买的国债，收益率远不如他们货币的扩张&lt;/p&gt;&#10;&lt;h4 id="外汇占款带来的货币扩张帮助了谁"&gt;外汇占款带来的货币扩张帮助了谁&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;补贴了出口部门&lt;/li&gt;&#10;&lt;li&gt;剥夺了中国内地居民，补贴了东南沿海制造业，正是由于要扶植东南沿海的出口制造业，才会有货币超发&lt;/li&gt;&#10;&lt;li&gt;内地背井离乡，抛家弃子，出售廉价劳动&lt;/li&gt;&#10;&lt;li&gt;沿海大量人口涌入，推高了房价，地方政府和本地居民称为地主&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="越难复制的资产，保值能力就越强"&gt;越难复制的资产，保值能力就越强&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;学区房&lt;/li&gt;&#10;&lt;li&gt;茅台年份酒&lt;/li&gt;&#10;&lt;li&gt;比特币&lt;/li&gt;&#10;&lt;li&gt;国内股票很垃圾&lt;br&gt;&#10;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/money/agu.png" alt="image"&gt;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>用strip和objcopy精简二进制</title><link>https://www.oneyearago.me/2021/06/19/strip-and-objcopy/</link><pubDate>Sat, 19 Jun 2021 02:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/06/19/strip-and-objcopy/</guid><description>&lt;h4 id="问题-设置到-LD-PRELOAD的动态库-找不到符号"&gt;问题: 设置到 LD_PRELOAD的动态库 找不到符号&lt;/h4&gt;&#10;&lt;p&gt;在设置了优先加载的 动态库以后，先加载的库里面的函数与 后加载的库里面的函数可能是有冲突的，比如遇到的这个例子，先加在的动态库中其实不存在完整的函数实现，只有符号表，并不能执行，但是它被设置成先加载了。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/os/link-error-strip.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;对于有多个动态库中存在同一个符号的，一定程度上，我们可以从某个库 中 裁剪去这个符号（函数），去加载另外一个库里面的函数。&lt;/p&gt;&#10;&lt;h4 id="agent-api-init-在静态库和动态库中都存在"&gt;agent_api_init 在静态库和动态库中都存在&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# nm 3rdparty/libs/libtbus.a | grep agent_api_init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; U agent_api_init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# nm build/build/libhook.so | grep agent_api_init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; U agent_api_init&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="使用strip-可以指定删减-符号表"&gt;使用strip 可以指定删减 符号表&lt;/h4&gt;&#10;&lt;p&gt;去除前后的比较&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[root@VM_8_16_centos ~]# strip -R agent_api_init libtbus.a -o nn.a&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[root@VM_8_16_centos ~]# nm libtbus.a | grep agent_api_init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; U agent_api_init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[root@VM_8_16_centos ~]# nm nn.a | grep agent_api_init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nm: tbus.o: no symbols&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nm: tbus_addr_templet.o: no symbols&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;......&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nm: tbus_route.o: no symbols&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="objcopy-不能去除-重定位的符号表"&gt;objcopy 不能去除 重定位的符号表&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[root@VM_8_16_centos ~]# objcopy --strip-symbol agent_api_init libtbus.a nnn.a&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;objcopy: not stripping symbol `agent_api_init&amp;#39; because it is named in a relocatio&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="也可以直接去掉动态库的符号"&gt;也可以直接去掉动态库的符号&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# strip -R agent_api_init libhook.so -o new_hook.so &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# nm new_hook.so | grep agent_api_init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nm: new_hook.so: no symbols&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;去掉libhook.so里面的 无用符号以后，加载器找函数执行的时候就能找到正确的so， 不再有运行时链接错误。&lt;/p&gt;</description></item><item><title>渴望明天</title><link>https://www.oneyearago.me/2021/06/07/tcp/</link><pubDate>Mon, 07 Jun 2021 21:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/06/07/tcp/</guid><description>&lt;h3 id="渴望明天"&gt;渴望明天&lt;/h3&gt;&#10;&lt;p&gt;2021年高考，看到个图，挺好玩的，作为标题吧&lt;br&gt;&#10;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/net/wait-tomorrow.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="哈哈哈-TCP"&gt;哈哈哈 TCP&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/net/tcp-socket.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/net/network-api.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="三次握手主要做什么"&gt;三次握手主要做什么&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;同步 初始序列号&lt;/li&gt;&#10;&lt;li&gt;同步 MSS&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/net/tcp-handshake.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="四次挥手一定是四次吗？"&gt;四次挥手一定是四次吗？&lt;/h3&gt;&#10;&lt;p&gt;很容易抓到 三次挥手的包&lt;br&gt;&#10;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/net/goodbye-tcp.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="MSS-amp-MTU"&gt;MSS &amp;amp; MTU&lt;/h3&gt;&#10;&lt;h4 id="尽可能避免IP的分片"&gt;尽可能避免IP的分片&lt;/h4&gt;&#10;&lt;p&gt;网络通信，尽一切可能避免IP的分片！为什么？因为负责IP分片的那台主机、路由器会花费很多CPU资源来处理分片，同时负责重组IP分片的主机、路由器则需要更多的CPU资源来重组这些IP包的分片。比如一台路由器的数据处理能力是10G，如果处理分片则降低到2G左右，这还是比较高端的平台，采用硬件，软件CPU一起协作才达到的水平，如果比较低端的平台，纯粹采用CPU软件来处理，那数据包处理能力简直惨不忍睹！&lt;/p&gt;&#10;&lt;h4 id="MSS"&gt;MSS&lt;/h4&gt;&#10;&lt;p&gt;一般网络接口都有一个最大传输单元即MTU，比如MTU=1500，什么意思呢？如果IP包的尺寸&amp;lt;= 1500，原封不动从这个网络接口发送，否则就需要分片。&lt;br&gt;&#10;那聪明的你一定会说，既然知道MTU=1500，那为什么要发超过1500的包呢？给自己找不自在？既然IP包最大1500了，那如果TCP=1480不就可以了吗？&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;IP = IP Header + TCP = 20 + 1480 = 1500&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;TCP = TCP Header + TCP Payload = 20 + 1460 = 1480&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个TCP Payload = 1460 对应的就是TCP最大传输单元，我们称之为“ Maximum Segment Size” 即MSS，那么MSS= MTU - IP Header - TCP Header = 1500 - 20 -20 = 1460。&lt;/p&gt;</description></item><item><title>tcpdump note</title><link>https://www.oneyearago.me/2021/05/30/tcpdump/</link><pubDate>Sun, 30 May 2021 21:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/05/30/tcpdump/</guid><description>&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -v -nn -X -v &amp;#39;port 80&amp;#39; -c 5 -w cap.tcpdump&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="选项"&gt;选项&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;i, interface 指定监听哪个网卡&lt;/li&gt;&#10;&lt;li&gt;nn, 如遇到21这类的端口号，显示21而不是 ftp&lt;/li&gt;&#10;&lt;li&gt;X, 显示16进制和 ASCII码&lt;/li&gt;&#10;&lt;li&gt;‘port 53’ 过滤端口&lt;/li&gt;&#10;&lt;li&gt;c : count 包个数&lt;/li&gt;&#10;&lt;li&gt;v : verbose&lt;/li&gt;&#10;&lt;li&gt;F : filter by file; tcpdump -i eth0 -v -nn -X -v -c 5 -F filter.txt&lt;/li&gt;&#10;&lt;li&gt;w : dump to file&lt;/li&gt;&#10;&lt;li&gt;r : tcpdump -r cap.tcpdump&lt;/li&gt;&#10;&lt;li&gt;s : size,包的大小，默认65535，可以调大&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="过滤"&gt;过滤&lt;/h3&gt;&#10;&lt;h4 id="按照协议过滤"&gt;按照协议过滤&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -i eth0 -c 10 &amp;#39;udp&amp;#39; ## 只要 udp&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;协议: ether, ip, ip6, arp, tcp, rarp …&lt;/p&gt;</description></item><item><title>gdb指令级别单步调试</title><link>https://www.oneyearago.me/2021/05/18/gdb-step-debug/</link><pubDate>Tue, 18 May 2021 21:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/05/18/gdb-step-debug/</guid><description>&lt;h3 id="gdb单步调试"&gt;gdb单步调试&lt;/h3&gt;&#10;&lt;h4 id="原来服务的的启动方式"&gt;原来服务的的启动方式&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/data/home/2333/xx/matchsvr/bin/matchsvr --noloadconf --conf-file=../conf/matchsvr_conf.xml &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --log-file=../log --tlogconf=../conf/tlogconf.xml --id=28.233.0.1 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --business-id=0 -D restart&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="gdb拉起，设置命令行参数"&gt;gdb拉起，设置命令行参数&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ gdb matchsvr&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;set args --noloadconf --conf-file=../conf/matchsvr_conf.xml &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --log-file=../log --tlogconf=../conf/tlogconf.xml --id=28.233.0.1 --business-id=0 -D restart&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="处理守护进程"&gt;处理守护进程&lt;/h4&gt;&#10;&lt;p&gt;对于守护进程，实际服务的进程是fork两次后的子进程，我们在父进程打断点是起不到作用的&lt;/p&gt;&#10;&lt;h4 id="set-follow-fork-mode-parent-child"&gt;set follow-fork-mode [parent|child]&lt;/h4&gt;&#10;&lt;p&gt;parent: fork之后继续调试父进程，子进程不受影响。&lt;/p&gt;&#10;&lt;p&gt;child: fork之后调试子进程，父进程不受影响。&lt;/p&gt;&#10;&lt;p&gt;如果需要调试子进程，在启动gdb后：&lt;/p&gt;&#10;&lt;p&gt;(gdb) set follow-fork-mode child&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) set detach-on-fork on&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) set follow-fork-mode child&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="打断点的几种方式"&gt;打断点的几种方式&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) b tbuspphook::hook_tbuspp::Peek &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) b ua::TBusppChannel::OnRecv&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) b hook_tbuspp.h:108 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) r &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) c&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) p pCall&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$1 = (int (tbuspphook::hook_tbuspp::*)(tbuspphook::hook_tbuspp * const, int *, const char **...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="设置单步调试"&gt;设置单步调试&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) set disassemble-next-line on&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;按照指令级别 单步调试&lt;/p&gt;</description></item><item><title>redis消息队列</title><link>https://www.oneyearago.me/2021/04/20/redis-mq/</link><pubDate>Tue, 20 Apr 2021 21:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/04/20/redis-mq/</guid><description>&lt;h3 id="消息队列的特征"&gt;消息队列的特征&lt;/h3&gt;&#10;&lt;p&gt;消息队列在存取消息时，必须要满足三个需求，分别是&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;消息保序&lt;/li&gt;&#10;&lt;li&gt;处理重复的消息&lt;/li&gt;&#10;&lt;li&gt;保证消息可靠性&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="消息保序"&gt;消息保序&lt;/h4&gt;&#10;&lt;p&gt;对于 单队列，单进程的queue， 是满足先入先出的特点的，本身是有序的，但是如果有多个队列或多个消费者线程的时候，可能会产生乱序的问题。&lt;/p&gt;&#10;&lt;p&gt;kafka的例子:&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/kafka-rand-seq.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/kafka-rand-seq-solu.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;stream的缺点就是在redis内部，stream就是一个单一的key，如果不对key进行分片，那么stream的容量被限制在单个redis的实例，&lt;br&gt;&#10;当然我们可以使用redis cluster对stream的key进行分片，实现类似kafka多partition的概念，但是由于redis cluster的一些限制，需要解决redis原生命令不支持跨slot操作的问题，&lt;br&gt;&#10;当然经过合理的设计，这并不是一个很大的问题，但是也需要解决，因此如果mq中的数据过于庞大，单个redis实例已经无法容纳，我们需要对stream的key进行分片并解决一些问题，这增大了业务实现的难度.&lt;/p&gt;&#10;&lt;h4 id="重复消息"&gt;重复消息&lt;/h4&gt;&#10;&lt;p&gt;比如 RabbitMQ、RocketMQ、Kafka，都有可能会出现消息重复消费的问题，正常。因为这问题通常不是 MQ 自己保证的，是由我们开发来保证的。挑一个 Kafka 来举个例子，说说怎么重复消费吧。&lt;/p&gt;&#10;&lt;p&gt;Kafka 实际上有个 offset 的概念，就是每个消息写进去，都有一个 offset，代表消息的序号，然后 consumer 消费了数据之后，每隔一段时间（定时定期），会把自己消费过的消息的 offset 提交一下，表示“我已经消费过了，下次我要是重启啥的，&lt;br&gt;&#10;你就让我继续从上次消费到的 offset 来继续消费吧”。&lt;/p&gt;&#10;&lt;p&gt;但是凡事总有意外，比如我们之前生产经常遇到的，就是你有时候重启系统，看你怎么重启了，如果碰到点着急的，直接 kill 进程了，再重启。这会导致 consumer 有些消息处理了，但是没来得及提交 offset，尴尬了。重启之后，少数消息会再次消费一次。&lt;/p&gt;&#10;&lt;p&gt;当收到一条消息后，消费者程序就可以对比收到的消息 ID 和记录的已处理过的消息 ID，来判断当前收到的消息有没有经过处理。如果已经处理过，那么，消费者程序就不再进行处理了。这种处理特性也称为幂等性，&lt;br&gt;&#10;幂等性就是指，对于同一条消息，消费者收到一次的处理结果和收到多次的处理结果是一致的。&lt;/p&gt;&#10;&lt;p&gt;基于 Streams 的消息队列解决方案:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;XADD：插入消息，保证有序，可以自动生成全局唯一 ID&lt;/li&gt;&#10;&lt;li&gt;XREAD：用于读取消息，可以按 ID 读取数据&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="可靠性"&gt;可靠性&lt;/h4&gt;&#10;&lt;p&gt;基于 Streams 的消息队列解决方案:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;XPENDING 命令可以用来查询每个消费组内所有消费者已读取但尚未确认的消息，而 XACK 命令用于向消息队列确认消息处理已完成&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;在用Redis当作队列或存储数据时，是有可能丢失数据的：一个场景是，如果打开AOF并且是每秒写盘，因为这个写盘过程是异步的，Redis宕机时会丢失1秒的数据。而如果AOF改为同步写盘，那么写入性能会下降。&lt;br&gt;&#10;另一个场景是，如果采用主从集群，如果写入量比较大，从库同步存在延迟，此时进行主从切换，也存在丢失数据的可能（从库还未同步完成主库发来的数据就被提成主库）。&lt;br&gt;&#10;总的来说，Redis不保证严格的数据完整性和主从切换时的一致性。我们在使用Redis时需要注意。&lt;/p&gt;&#10;&lt;p&gt;而采用RabbitMQ和Kafka这些专业的队列中间件时，就没有这个问题了。这些组件一般是部署一个集群，生产者在发布消息时，队列中间件一般会采用写多个节点+预写磁盘的方式保证消息的完整性，即便其中一个节点挂了，也能保证集群的数据不丢失。&lt;/p&gt;&#10;&lt;p&gt;如果是金融相关的业务场景，例如交易、支付这类，建议还是使用专业的队列中间件。&lt;/p&gt;&#10;&lt;h4 id="用-List-和-Streams-实现消息队列的特点和区别"&gt;用 List 和 Streams 实现消息队列的特点和区别&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/redis-list-streams-mq.jpg" alt="image"&gt;&lt;/p&gt;</description></item><item><title>redis数据冗余与分片</title><link>https://www.oneyearago.me/2021/04/19/redis-replica-and-redundancy/</link><pubDate>Mon, 19 Apr 2021 19:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/04/19/redis-replica-and-redundancy/</guid><description>&lt;h3 id="redis数据冗余与分片"&gt;redis数据冗余与分片&lt;/h3&gt;&#10;&lt;h4 id="数据冗余-主从复制"&gt;数据冗余-主从复制&lt;/h4&gt;&#10;&lt;p&gt;Redis 提供了主从库模式，以保证数据副本的一致，主从库之间采用的是读写分离的方式。&lt;br&gt;&#10;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/rds-commu.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;读操作：主库、从库都可以接收；&lt;/li&gt;&#10;&lt;li&gt;写操作：首先到主库执行，然后，主库将写操作同步给从库。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="主从库间如何进行第一次同步？"&gt;主从库间如何进行第一次同步？&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;replicaof 172.16.19.3 6379&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/redis-replicaof.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="断网增量更新"&gt;断网增量更新&lt;/h4&gt;&#10;&lt;p&gt;当主从库断连后，主库会把断连期间收到的写操作命令，写入 replication buffer，同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/redis-diff-push.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="主从全量同步使用RDB而不使用AOF的原因"&gt;主从全量同步使用RDB而不使用AOF的原因&lt;/h4&gt;&#10;&lt;p&gt;RDB文件内容是经过压缩的二进制数据（不同数据类型数据做了针对性优化），文件很小。而AOF文件记录的是每一次写操作的命令，写操作越多文件会变得很大，其中还包括很多对同一个key的多次冗余操作。&lt;/p&gt;&#10;&lt;h4 id="哨兵机制"&gt;哨兵机制&lt;/h4&gt;&#10;&lt;p&gt;Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案，当用Redis做Master-slave的高可用方案时，假如master宕机了，Redis本身(包括它的很多客户端)都没有实现自动进行主备切换，而Redis-sentinel本身也是一个独立运行的进程，它能监控多个master-slave集群，发现master宕机后能进行自动切换。&lt;br&gt;&#10;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/rds-sentinel.png" alt="image"&gt;&lt;br&gt;&#10;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/rds-sentinel-util.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="如何选定新主库？"&gt;如何选定新主库？&lt;/h5&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;优先级最高的从库得分高。&lt;/li&gt;&#10;&lt;li&gt;和旧主库同步程度最接近的从库得分高。&lt;/li&gt;&#10;&lt;li&gt;ID 号小的从库得分高。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="数据分片"&gt;数据分片&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/redis-cluster.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="客户端如何定位数据？"&gt;客户端如何定位数据？&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/redis-cluster-choose.jpg" alt="image"&gt;&lt;/p&gt;</description></item><item><title>redis持久化</title><link>https://www.oneyearago.me/2021/04/17/redis-persistence/</link><pubDate>Sat, 17 Apr 2021 11:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/04/17/redis-persistence/</guid><description>&lt;h3 id="AOF-机制-Append-Only-File"&gt;AOF 机制 (Append Only File)&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/rds-aof.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="flush时间配置"&gt;flush时间配置&lt;/h4&gt;&#10;&lt;p&gt;AOF 配置项 appendfsync 的三个可选值:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Always，同步写回：每个写命令执行完，立马同步地将日志写回磁盘；&lt;/li&gt;&#10;&lt;li&gt;Everysec，每秒写回：每个写命令执行完，只是先把日志写到 AOF 文件的内存缓冲区，每隔一秒把缓冲区中的内容写入磁盘；&lt;/li&gt;&#10;&lt;li&gt;No，操作系统控制的写回：每个写命令执行完，只是先把日志写到 AOF 文件的内存缓冲区，由操作系统决定何时将缓冲区内容写回磁盘。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="aof-重写机制"&gt;aof 重写机制&lt;/h4&gt;&#10;&lt;p&gt;简单来说，AOF 重写机制就是在重写时，Redis 根据数据库的现状创建一个新的 AOF 文件，也就是说，读取数据库中的所有键值对，然后对每一个键值对用一条命令记录它的写入。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/aof-rewrite.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;重写的过程&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;从主进程中fork出子进程，并拿到fork时的AOF文件数据写到一个临时AOF文件中&lt;/li&gt;&#10;&lt;li&gt;在重写过程中，redis收到的命令会同时写到AOF缓冲区和重写缓冲区中，这样保证重写不丢失重写过程中的命令&lt;/li&gt;&#10;&lt;li&gt;重写完成后通知主进程，主进程会将AOF缓冲区中的数据追加到子进程生成的文件中&lt;/li&gt;&#10;&lt;li&gt;redis会原子的将旧文件替换为新文件，并开始将数据写入到新的aof文件上&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="AOF-重写的操作系统过程"&gt;AOF 重写的操作系统过程&lt;/h4&gt;&#10;&lt;p&gt;fork子进程，fork这个瞬间一定是会阻塞主线程的，fork采用操作系统提供的写实复制(Copy On Write)机制，就是为了避免一次性拷贝大量内存数据给子进程造成的长时间阻塞问题，但fork子进程需要拷贝进程必要的数据结构，其中有一项就是拷贝内存页表（虚拟内存和物理内存的映射索引表），这个拷贝过程会消耗大量CPU资源，拷贝完成之前整个进程是会阻塞的，阻塞时间取决于整个实例的内存大小，实例越大，内存页表越大，fork阻塞时间越久。&lt;/p&gt;&#10;&lt;p&gt;拷贝内存页表完成后，子进程与父进程指向相同的内存地址空间，也就是说此时虽然产生了子进程，但是并没有申请与父进程相同的内存大小。那什么时候父子进程才会真正内存分离呢？“写时复制”顾名思义，就是在写发生时，才真正拷贝内存真正的数据，这个过程中，父进程也可能会产生阻塞的风险，就是下面介绍的场景。&lt;/p&gt;&#10;&lt;p&gt;fork出的子进程指向与父进程相同的内存地址空间，此时子进程就可以执行AOF重写，把内存中的所有数据写入到AOF文件中。但是此时父进程依旧是会有流量写入的，如果父进程操作的是一个已经存在的key，那么这个时候父进程就会真正拷贝这个key对应的内存数据，申请新的内存空间，这样逐渐地，父子进程内存数据开始分离，父子进程逐渐拥有各自独立的内存空间。&lt;/p&gt;&#10;&lt;p&gt;因为内存分配是以页为单位进行分配的，默认4k，如果父进程此时操作的是一个bigkey，重新申请大块内存耗时会变长，可能会产阻塞风险。另外，如果操作系统开启了内存大页机制(Huge Page，页面大小2M)，那么父进程申请内存时阻塞的概率将会大大提高，所以在Redis机器上需要关闭Huge Page机制。Redis每次fork生成RDB或AOF重写完成后，都可以在Redis log中看到父进程重新申请了多大的内存空间。&lt;/p&gt;&#10;&lt;h3 id="快照"&gt;快照&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/rds-snapshotting.jpg" alt="image"&gt;&lt;br&gt;&#10;理论跟 aof重写机制类似， fork之后,遍历k-v按照一定格式做镜像；后面在按照这个镜像恢复&lt;/p&gt;&#10;&lt;h4 id="混合使用-AOF-日志和内存快照"&gt;混合使用 AOF 日志和内存快照&lt;/h4&gt;&#10;&lt;p&gt;内存快照以一定的频率执行，在两次快照之间，使用 AOF 日志记录这期间的所有命令操作。&lt;/p&gt;&#10;&lt;h4 id="风险"&gt;风险&lt;/h4&gt;&#10;&lt;p&gt;如果Redis进程绑定了CPU，那么子进程会继承父进程的CPU亲和性属性，子进程必然会与父进程争夺同一个CPU资源，整个Redis Server的性能必然会受到影响！所以如果Redis需要开启定时RDB和AOF重写，进程一定不要绑定CPU。&lt;/p&gt;</description></item><item><title>redis基础</title><link>https://www.oneyearago.me/2021/04/10/redis-basic/</link><pubDate>Sat, 10 Apr 2021 11:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/04/10/redis-basic/</guid><description>&lt;h3 id="如果你只是急于解决太多细微的问题，能力就很难得到质的提升"&gt;如果你只是急于解决太多细微的问题，能力就很难得到质的提升&lt;/h3&gt;&#10;&lt;h3 id="Redis-学习的路线"&gt;Redis 学习的路线&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/redis_overview.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="底层数据结构"&gt;底层数据结构&lt;/h3&gt;&#10;&lt;h4 id="基础数据结构"&gt;基础数据结构&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/rds-data-struct.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="key-value的管理方式"&gt;key-value的管理方式&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/rds-kv-hash.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;如果哈希表里写入的数据越来越多，哈希冲突可能也会越来越多，这就会导致某些哈希冲突链过长，进而导致这个链上的元素查找耗时长，效率降低。Redis 默认使用了两个全局哈希表：哈希表 1 和哈希表 2。一开始，当你刚插入数据时，默认使用哈希表 1，此时的哈希表 2 并没有被分配空间。随着数据逐步增多，Redis 开始执行 rehash，&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;给哈希表 2 分配更大的空间，例如是当前哈希表 1 大小的两倍；&lt;/li&gt;&#10;&lt;li&gt;把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中；&lt;/li&gt;&#10;&lt;li&gt;释放哈希表 1 的空间。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="跳表"&gt;跳表&lt;/h4&gt;&#10;&lt;p&gt;skiplist不要求上下相邻两层链表之间的节点个数有严格的对应关系，而是为每个节点随机出一个层数(level)。比如，一个节点随机出的层数是3，那么就把它链入到第1层到第3层这三层链表中。为了表达清楚，下图展示了如何通过一步步的插入操作从而形成一个skiplist的过程：&lt;br&gt;&#10;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/skiplist_insertions.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="skiplist与平衡树、哈希表的比较"&gt;skiplist与平衡树、哈希表的比较&lt;/h5&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;skiplist和各种平衡树（如AVL、红黑树等）的元素是有序排列的，而哈希表不是有序的。因此，在哈希表上只能做单个key的查找，不适宜做范围查找。所谓范围查找，指的是查找那些大小在指定的两个值之间的所有节点。&lt;/li&gt;&#10;&lt;li&gt;在做范围查找的时候，平衡树比skiplist操作要复杂。在平衡树上，我们找到指定范围的小值之后，还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造，这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单，只需要在找到小值之后，对第1层链表进行若干步的遍历就可以实现。&lt;/li&gt;&#10;&lt;li&gt;平衡树的插入和删除操作可能引发子树的调整，逻辑复杂，而skiplist的插入和删除只需要修改相邻节点的指针，操作简单又快速。&lt;/li&gt;&#10;&lt;li&gt;从内存占用上来说，skiplist比平衡树更灵活一些。一般来说，平衡树每个节点包含2个指针（分别指向左右子树），而skiplist每个节点包含的指针数目平均为1/(1-p)，具体取决于参数p的大小。如果像Redis里的实现一样，取p=1/4，那么平均每个节点包含1.33个指针，比平衡树更有优势。&lt;/li&gt;&#10;&lt;li&gt;查找单个key，skiplist和平衡树的时间复杂度都为O(log n)，大体相当；而哈希表在保持较低的哈希值冲突概率的前提下，查找时间复杂度接近O(1)，性能更高一些。所以我们平常使用的各种Map或dictionary结构，大都是基于哈希表实现的。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="redis的线程模型"&gt;redis的线程模型&lt;/h3&gt;&#10;&lt;p&gt;Redis 是单线程，主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的，这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能，比如持久化、异步删除、集群数据同步等，其实是由额外的线程执行的。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/redis/rds-epoll-io.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="ref"&gt;ref&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="http://zhangtielei.com/posts/blog-redis-skiplist.html"&gt;http://zhangtielei.com/posts/blog-redis-skiplist.html&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://lotabout.me/2018/skip-list/"&gt;https://lotabout.me/2018/skip-list/&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>C++ 11 Atomic</title><link>https://www.oneyearago.me/2021/03/23/c++11-atomic/</link><pubDate>Tue, 23 Mar 2021 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/03/23/c++11-atomic/</guid><description>&lt;h3 id="引入的原子指令"&gt;引入的原子指令&lt;/h3&gt;&#10;&lt;p&gt;SSE2 extensions introduce two new fence instructions (LFENCE and MFENCE) as companions to the SFENCE instruction introduced with SSE extensions. The LFENCE instruction establishes a memory fence for loads. It guarantees ordering between two loads and prevents speculative loads from passing the load fence (that is, no speculative loads are allowed until all loads specified before the load fence have been carried out). The MFENCE instruction establishes a memory fence for both loads and stores. The processor ensures that no load or store after MFENCE will become globally visible until all loads and stores before MFENCE are globally visible.1 Note that the sequences LFENCE;SFENCE and SFENCE;LFENCE are not equivalent to MFENCE because neither ensures that older stores are globally observed prior to younger loads.&lt;/p&gt;</description></item><item><title>C++11 lambda</title><link>https://www.oneyearago.me/2021/03/23/c++11-lambda/</link><pubDate>Tue, 23 Mar 2021 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/03/23/c++11-lambda/</guid><description>&lt;h3 id="Introduction"&gt;Introduction&lt;/h3&gt;&#10;&lt;p&gt;C ++ 11引入了lambda，该lambda提供了一种语法上轻量级的方式来动态定义函数。它们还可以通过值或引用来捕获（或封闭）周围范围的变量。在本文中，我们将研究lambda与纯函数和函子类（实现的类）在实现方面的区别operator()。&lt;/p&gt;&#10;&lt;h4 id="Capture-By-Value"&gt;Capture By Value&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;class Functor {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Functor(const int x): m_x(x) {}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int operator()(int a) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return a + m_x;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; private:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int m_x;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int main() {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int x = 3;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; auto lambda = [=] (int a) { return a + x; };&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Functor functor(x);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; volatile int y1 = functor(5);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; volatile int y2 = lambda(5);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;按值捕获lambda的工作方式与标准functor几乎相同：它们都分配存储捕获值的对象，并获取指向该对象的隐藏函数参数。lambda和functor的函数调用执行的代码是相同的。唯一的区别是lambda的构造函数被内联到创建lambda的函数中，而不是像函子的构造函数那样是一个单独的函数。&lt;/p&gt;</description></item><item><title>ddia-事务</title><link>https://www.oneyearago.me/2021/03/20/ddia-7-transaction/</link><pubDate>Sat, 20 Mar 2021 22:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/03/20/ddia-7-transaction/</guid><description>&lt;h3 id="transaction"&gt;transaction&lt;/h3&gt;&#10;&lt;p&gt;事务是一个抽象层，允许应用程序假装某些并发问题和某些类型的硬件和软件故障不存在。各式各样的错误被简化为一种简单情况：事务中止（transaction abort），而应用需要的仅仅是重试。&lt;/p&gt;&#10;&lt;p&gt;如果没有事务处理，各种错误情况（进程崩溃，网络中断，停电，磁盘已满，意外并发等）意味着数据可能以各种方式变得不一致。例如，非规范化的数据可能很容易与源数据不同步。如果没有事务处理，就很难推断复杂的交互访问可能对数据库造成的影响。&lt;/p&gt;&#10;&lt;p&gt;本章深入讨论了并发控制的话题。我们讨论了几个广泛使用的隔离级别，特别是:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;读已提交，&lt;/li&gt;&#10;&lt;li&gt;快照隔离（有时称为可重复读）&lt;/li&gt;&#10;&lt;li&gt;可序列化&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;并通过研究竞争条件的各种例子，来描述这些隔离等级：&lt;/p&gt;&#10;&lt;h4 id="脏读"&gt;脏读&lt;/h4&gt;&#10;&lt;p&gt;一个客户端读取到另一个客户端尚未提交的写入。读已提交或更强的隔离级别可以防止脏&lt;br&gt;&#10;读。&lt;br&gt;&#10;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/os/dirty-read.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;要求读锁的办法在实践中效果并不好。因为一个长时间运行的写入事务会迫使许多只读&lt;br&gt;&#10;事务等到这个慢写入事务完成。这会损失只读事务的响应时间，并且不利于可操作性.&lt;/p&gt;&#10;&lt;p&gt;大多数数据库使用如下方式防止脏读：对于写入的每个对象，数据库都&lt;br&gt;&#10;会记住旧的已提交值，和由当前持有写入锁的事务设置的新值。 当事务正在进行时，任何其&lt;br&gt;&#10;他读取对象的事务都会拿到旧值。 只有当新值提交后，事务才会切换到读取新值。&lt;/p&gt;&#10;&lt;h4 id="脏写"&gt;脏写&lt;/h4&gt;&#10;&lt;p&gt;一个客户端覆盖写入了另一个客户端尚未提交的写入。几乎所有的事务实现都可以防止脏&lt;br&gt;&#10;写。&lt;/p&gt;&#10;&lt;p&gt;数据库通过使用行锁（row-level lock） 来防止脏写：当事务想要修改特定&lt;br&gt;&#10;对象（行或文档）时，它必须首先获得该对象的锁。然后必须持有该锁直到事务被提交或中&lt;br&gt;&#10;止。一次只有一个事务可持有任何给定对象的锁；如果另一个事务要写入同一个对象，则必&lt;br&gt;&#10;须等到第一个事务提交或中止后，才能获取该锁并继续。这种锁定是读已提交模式（或更强&lt;br&gt;&#10;的隔离级别）的数据库自动完成的。&lt;/p&gt;&#10;&lt;h4 id="读取偏差（不可重复读）"&gt;读取偏差（不可重复读）&lt;/h4&gt;&#10;&lt;p&gt;在同一个事务中，客户端在不同的时间点会看见数据库的不同状态。快照隔离经常用于解决&lt;br&gt;&#10;这个问题，它允许事务从一个特定时间点的一致性快照中读取数据。快照隔离通常使用多版&lt;br&gt;&#10;本并发控制（MVCC） 来实现。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/os/kuaizhao.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/os/kuaizhao_read5.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="更新丢失"&gt;更新丢失&lt;/h4&gt;&#10;&lt;p&gt;两个客户端同时执行读取-修改-写入序列。其中一个写操作，在没有合并另一个写入变更情&lt;br&gt;&#10;况下，直接覆盖了另一个写操作的结果。所以导致数据丢失。快照隔离的一些实现可以自动&lt;br&gt;&#10;防止这种异常，而另一些实现则需要手动锁定（ SELECT FOR UPDATE ）。&lt;/p&gt;&#10;&lt;p&gt;使用提供的原子操作或显示锁定&lt;/p&gt;&#10;&lt;h4 id="写偏差"&gt;写偏差&lt;/h4&gt;&#10;&lt;p&gt;一个事务读取一些东西，根据它所看到的值作出决定，并将决定写入数据库。但是，写作的&lt;br&gt;&#10;时候，决定的前提不再是真实的。只有可序列化的隔离才能防止这种异常。&lt;/p&gt;&#10;&lt;h4 id="幻读"&gt;幻读&lt;/h4&gt;&#10;&lt;p&gt;事务读取符合某些搜索条件的对象。另一个客户端进行写入，影响搜索结果。快照隔离可以&lt;br&gt;&#10;防止直接的幻像读取，但是写入歪斜环境中的幻影需要特殊处理，例如索引范围锁定。&lt;/p&gt;&#10;&lt;p&gt;弱隔离级别可以防止这些异常情况，但是让应用程序开发人员手动处理其他应用程序（例&lt;br&gt;&#10;如，使用显式锁定）。只有可序列化的隔离才能防范所有这些问题。&lt;/p&gt;&#10;&lt;h3 id="可序列化与两阶段锁"&gt;可序列化与两阶段锁&lt;/h3&gt;&#10;&lt;p&gt;数十年来，两阶段锁定一直是实现可序列化的标准方式，但是许多应用出于性能问题的考虑避免使用它。&lt;/p&gt;&#10;&lt;p&gt;在2PL中，写入不仅会阻塞其他写入，也会阻塞读，反之亦然。快照隔离使得读不阻塞写，写&lt;br&gt;&#10;也不阻塞读（参阅“实现快照隔离”），这是2PL和快照隔离之间的关键区别。另一方面，因为&lt;br&gt;&#10;2PL提供了可序列化的性质，它可以防止早先讨论的所有竞争条件，包括丢失更新和写入偏&lt;br&gt;&#10;差。&lt;/p&gt;&#10;&lt;h4 id="实现两阶段锁"&gt;实现两阶段锁&lt;/h4&gt;&#10;&lt;p&gt;读与写的阻塞是通过为数据库中每个对象添加锁来实现的。锁可以处于共享模式（shared&lt;br&gt;&#10;mode）或独占模式（exclusive mode）。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;若事务要读取对象，则须先以共享模式获取锁。允许多个事务同时持有共享锁。但如果另一个事务已经在对象上持有排它锁，则这些事务必须等待&lt;/li&gt;&#10;&lt;li&gt;若事务要写入一个对象，它必须首先以独占模式获取该锁。没有其他事务可以同时持有锁（无论是共享模式还是独占模式），所以如果对象上存在任何锁，该事务必须等待。&lt;/li&gt;&#10;&lt;li&gt;如果事务先读取再写入对象，则它可能会将其共享锁升级为独占锁。升级锁的工作与直接获得排他锁相同。&lt;/li&gt;&#10;&lt;li&gt;事务获得锁之后，必须继续持有锁直到事务结束（提交或中止）。这就是“两阶段”这个名字的来源：第一阶段（当事务正在执行时）获取锁，第二阶段（在事务结束时）释放所有的锁。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="例子"&gt;例子&lt;/h5&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/os/2-phase-lock.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;事务 B 的 update 语句会被阻塞，直到事务 A 执行 commit 之后，事务 B 才能继续执行。&lt;/p&gt;</description></item><item><title>ddia 存储与检索</title><link>https://www.oneyearago.me/2021/03/16/ddia-03-storage-and-index/</link><pubDate>Tue, 16 Mar 2021 21:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/03/16/ddia-03-storage-and-index/</guid><description>&lt;h3 id="存储与检索"&gt;存储与检索&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/os/storage-and-index.svg" alt="image"&gt;&lt;/p&gt;</description></item><item><title>理解右值引用</title><link>https://www.oneyearago.me/2021/03/08/understand-r-reference/</link><pubDate>Mon, 08 Mar 2021 21:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/03/08/understand-r-reference/</guid><description>&lt;h4 id="一句话概述"&gt;一句话概述&lt;/h4&gt;&#10;&lt;p&gt;std::move本身只做类型转换，对性能无影响。&lt;br&gt;&#10;我们可以在自己的类中实现移动语义，避免深拷贝，充分利用右值引用和std::move的语言特性。&lt;/p&gt;&#10;&lt;p&gt;移动语义目的就是用浅拷贝代替深拷贝，右值引用跟深拷贝放到同一场景才是有意义的。&lt;/p&gt;&#10;&lt;h4 id="实现移动语义"&gt;实现移动语义&lt;/h4&gt;&#10;&lt;p&gt;在没有右值引用之前，一个简单的数组类通常实现如下，有构造函数、拷贝构造函数、赋值运算符重载、析构函数等。深拷贝/浅拷贝在此不做讲解&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;class Array {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Array(int size) : size_(size) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; data = new int[size_];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; // 深拷贝构造&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Array(const Array&amp;amp; temp_array) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; size_ = temp_array.size_;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; data_ = new int[size_];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; for (int i = 0; i &amp;lt; size_; i ++) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; data_[i] = temp_array.data_[i];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; // 深拷贝赋值&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Array&amp;amp; operator=(const Array&amp;amp; temp_array) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete[] data_;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; size_ = temp_array.size_;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; data_ = new int[size_];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; for (int i = 0; i &amp;lt; size_; i ++) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; data_[i] = temp_array.data_[i];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ~Array() {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete[] data_;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int *data_;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int size_;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;该类的拷贝构造函数、赋值运算符重载函数已经通过使用左值引用传参来避免一次多余拷贝了，但是内部实现要深拷贝，无法避免。&lt;/p&gt;</description></item><item><title>一个Linux 动态库常见bug</title><link>https://www.oneyearago.me/2021/01/13/a-linux-shared-lib-bug/</link><pubDate>Wed, 13 Jan 2021 17:22:39 +0800</pubDate><guid>https://www.oneyearago.me/2021/01/13/a-linux-shared-lib-bug/</guid><description>&lt;h3 id="遇到的问题"&gt;遇到的问题&lt;/h3&gt;&#10;&lt;p&gt;对于正在运行的程序，当更新动态库尝试重新加载时，可能会导致程序 CoreDump 。&lt;/p&gt;&#10;&lt;h5 id="case"&gt;case:&lt;/h5&gt;&#10;&lt;p&gt;我们的一个开发环境中有多个进程依赖同一个动态库.so， 我更新这个so的时候，直接用&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp new.so old.so&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;导致多个进程同时core,😮😮😮&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog21-1251969284.cos.ap-shanghai.myqcloud.com/os/linux-shared-lib.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;这里简单解释下其原因，以及规避方案。&lt;/p&gt;&#10;&lt;h3 id="简介"&gt;简介&lt;/h3&gt;&#10;&lt;p&gt;在加载动态库时，主要通过如下方式进行测试。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;加载执行关闭，cp覆盖文件，加载执行，正常。&lt;/li&gt;&#10;&lt;li&gt;加载执行不关闭，cp覆盖文件，加载执行，CoreDump。&lt;/li&gt;&#10;&lt;li&gt;加载执行不关闭，install覆盖文件，关闭加载执行，正常。&lt;/li&gt;&#10;&lt;li&gt;加载执行不关闭，install覆盖文件，加载执行关闭关闭，第二次加载的是同一个文件。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/* cat foobar.c */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;stdio.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;dlfcn.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;unistd.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;stdlib.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;typedef int (*CAC_FUNC)(int, int);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int main(void)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; void *handle1, *handle2;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; char *err;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; CAC_FUNC func = NULL;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if ((handle1 = dlopen(&amp;#34;./libcaculate.so&amp;#34;, RTLD_LAZY)) == NULL) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fprintf(stderr, &amp;#34;!!! %s\n&amp;#34;, dlerror());&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(EXIT_FAILURE);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; func = (CAC_FUNC)dlsym(handle1, &amp;#34;add&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if ((err = dlerror()) != NULL) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fprintf(stderr, &amp;#34;!!! %s\n&amp;#34;, err);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(EXIT_FAILURE);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;add: %d\n\n&amp;#34;, (func)(2,7));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dlclose(handle1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep(10);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if ((handle2 = dlopen(&amp;#34;./libcaculate.so&amp;#34;, RTLD_LAZY)) == NULL) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fprintf(stderr, &amp;#34;!!! %s\n&amp;#34;, dlerror());&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(EXIT_FAILURE);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; func = (CAC_FUNC)dlsym(handle2, &amp;#34;add&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if ((err = dlerror()) != NULL) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fprintf(stderr, &amp;#34;!!! %s\n&amp;#34;, err);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(EXIT_FAILURE);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;add: %d\n\n&amp;#34;, (func)(2,7));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dlclose(handle2);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(EXIT_SUCCESS);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/* cat caculate.c */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;stdio.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;static int gint = 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int add(int a, int b)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;static global member address is %p %p\n&amp;#34;, &amp;amp;gint, add);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return (a + b + 5);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return (a + b);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Makefile&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;all:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; gcc -fPIC -shared caculate.c -o libcaculate.so&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; gcc -o foobar foobar.c -ldl&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="产生原因"&gt;产生原因&lt;/h4&gt;&#10;&lt;p&gt;直接复制文件过去，实际上对应的 inode 信息是不变的，可以简单通过 strace cp new old 核心内容如下：&lt;/p&gt;</description></item><item><title>c++虚函数表</title><link>https://www.oneyearago.me/2020/12/22/cpp-class-model/</link><pubDate>Tue, 22 Dec 2020 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/12/22/cpp-class-model/</guid><description>&lt;h3 id="概述"&gt;概述&lt;/h3&gt;&#10;&lt;p&gt;首先,相较于C语言，C++语言并没有额外增加内存消耗(确切说，在没有虚函数情况下)。 对于一个C++类对象,每个对象有独立的数据成员(非static),但是内存中成员函数只有一份,该类的所有对象共享成员函数。&lt;/p&gt;&#10;&lt;p&gt;编译器在编译阶段,进行函数的重构，即将成员函数进行非成员化。通过将this指针作为函数的第一个参数,通过this指针即可以找到对象的数据成员&lt;/p&gt;&#10;&lt;h3 id="使用GDB调试-C-虚函数"&gt;使用GDB调试 C++ 虚函数&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;class Base&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int a;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int b;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; virtual void function(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; virtual void function001(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Base(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; void NotVirtFunc(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;class Derive : public Base&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Derive(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int c;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; virtual void function(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; virtual void function006(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;class Test&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; public:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int vv;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; virtual void bar(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; virtual void foo(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Test(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;class Derive2 : public Derive, public Test&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Derive2(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int d2;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; virtual void function(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; virtual void function006(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; virtual void bar(){}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int main()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Base b; // 没有继承&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; b.function();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Derive d; // 继承&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; d.function();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Derive2* d2 = new Derive2(); //多继承&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; d2-&amp;gt;function();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete d2;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) p b&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$1 = {_vptr.Base = 0x400a90 &amp;lt;vtable for Base+16&amp;gt;, a = 0, b = 0}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(gdb) info vtbl b&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vtable for &amp;#39;Base&amp;#39; @ 0x400a90 (subobject @ 0x7fffffffe2e0):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[0]: 0x40081e &amp;lt;Base::function()&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[1]: 0x400828 &amp;lt;Base::function001()&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/intel/post-cpp-no-inherit.png" alt="image"&gt;&lt;/p&gt;</description></item><item><title>std::shared_ptr</title><link>https://www.oneyearago.me/2020/12/16/shared_ptr/</link><pubDate>Wed, 16 Dec 2020 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/12/16/shared_ptr/</guid><description>&lt;h3 id="shared-ptr"&gt;shared_ptr&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;template&amp;lt; class T &amp;gt; class shared_ptr; (C++11 起)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;多个shared_ptr管理同一个指针，仅当最后一个shared_ptr析构时，指针才被delete。这是怎么实现的呢？答案是：引用计数（reference counting）。引用计数指的是，所有管理同一个裸指针（raw pointer）的shared_ptr，都共享一个引用计数器，每当一个shared_ptr被赋值（或拷贝构造）给其它shared_ptr时，这个共享的引用计数器就加1，当一个shared_ptr析构或者被用于管理其它裸指针时，这个引用计数器就减1，如果此时发现引用计数器为0，那么说明它是管理这个指针的最后一个shared_ptr了，于是我们释放指针指向的资源。&lt;/p&gt;&#10;&lt;h4 id="shared-ptr的简单实现"&gt;shared_ptr的简单实现&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include&amp;lt;iostream&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include&amp;lt;mutex&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include&amp;lt;thread&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;using namespace std;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;template&amp;lt;class T&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;class Shared_Ptr{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;public:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Shared_Ptr(T* ptr = nullptr)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;:_pPtr(ptr)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;, _pRefCount(new int(1))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;, _pMutex(new mutex)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;{}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;~Shared_Ptr()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;Release();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Shared_Ptr(const Shared_Ptr&amp;lt;T&amp;gt;&amp;amp; sp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;:_pPtr(sp._pPtr)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;, _pRefCount(sp._pRefCount)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;, _pMutex(sp._pMutex)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;AddRefCount();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Shared_Ptr&amp;lt;T&amp;gt;&amp;amp; operator=(const Shared_Ptr&amp;lt;T&amp;gt;&amp;amp; sp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;//if (this != &amp;amp;sp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;if (_pPtr != sp._pPtr)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;// 释放管理的旧资源&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;Release();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;// 共享管理新对象的资源，并增加引用计数&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;_pPtr = sp._pPtr;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;_pRefCount = sp._pRefCount;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;_pMutex = sp._pMutex;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;AddRefCount();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;return *this;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;T&amp;amp; operator*(){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;return *_pPtr;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;T* operator-&amp;gt;(){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;return _pPtr;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;int UseCount() { return *_pRefCount; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;T* Get() { return _pPtr; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;void AddRefCount()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;_pMutex-&amp;gt;lock();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;++(*_pRefCount);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;_pMutex-&amp;gt;unlock();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;private:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;void Release()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;bool deleteflag = false;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;_pMutex-&amp;gt;lock();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;if (--(*_pRefCount) == 0)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;delete _pRefCount;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;delete _pPtr;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;deleteflag = true;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;_pMutex-&amp;gt;unlock();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;if (deleteflag == true)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;delete _pMutex;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;private:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;int *_pRefCount;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;T* _pPtr;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;mutex* _pMutex;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="错误用法"&gt;错误用法&lt;/h3&gt;&#10;&lt;h4 id="1-循环引用"&gt;1. 循环引用&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;struct ListNode&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;int _data;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;shared_ptr&amp;lt;ListNode&amp;gt; _prev;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;shared_ptr&amp;lt;ListNode&amp;gt; _next;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;~ListNode(){ cout &amp;lt;&amp;lt; &amp;#34;~ListNode()&amp;#34; &amp;lt;&amp;lt; endl; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int main()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;shared_ptr&amp;lt;ListNode&amp;gt; node1(new ListNode);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;shared_ptr&amp;lt;ListNode&amp;gt; node2(new ListNode);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;cout &amp;lt;&amp;lt; node1.use_count() &amp;lt;&amp;lt; endl;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;cout &amp;lt;&amp;lt; node2.use_count() &amp;lt;&amp;lt; endl;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;node1-&amp;gt;_next = node2;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;node2-&amp;gt;_prev = node1;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;cout &amp;lt;&amp;lt; node1.use_count() &amp;lt;&amp;lt; endl;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;cout &amp;lt;&amp;lt; node2.use_count() &amp;lt;&amp;lt; endl;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;system(&amp;#34;pause&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;return 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;解决方案：在引用计数的场景下，把节点中的_prev和_next改成weak_ptr就可以了&lt;/p&gt;</description></item><item><title>从hook的并发症理解x64指令格式</title><link>https://www.oneyearago.me/2020/11/26/hook-complications-and-x64-instrcution-format/</link><pubDate>Thu, 26 Nov 2020 17:22:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/11/26/hook-complications-and-x64-instrcution-format/</guid><description>&lt;h3 id="hook的并发症"&gt;hook的并发症&lt;/h3&gt;&#10;&lt;p&gt;可以理解成一个有意思的问题，假如地址 addr1 上有一个函数func1，长度为len, 将这个函数 整体换一个位置，挪到 addr2, 移动之后的函数成为func2&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;memcpy(addr2 ,addr1,len );&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;原来调用 func1 语法是:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;func1(arg1 , arg2 , arg3);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;问:&lt;br&gt;&#10;现在用如下的方式调用 func2 是否会发生异常？&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;func2(arg1 , arg2 , arg3);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;假如把 func1 的开头 修改为特定的 shellcode，改成 跳转 + 目标跳转地址(比如 0xFF2500000000 + func_addr)的格式，就是传统的 inline hook， 但是这种hook将 原来函数的指令 挪了位置，再次调用有的时候会崩溃， 有的时候称这种情况为 hook的 并发症。&lt;/p&gt;&#10;&lt;h3 id="思路"&gt;思路&lt;/h3&gt;&#10;&lt;p&gt;==以下仅讨论 x86_64的情况，不涉及arm及其他体系结构。==&lt;/p&gt;&#10;&lt;p&gt;函数内部是一条条的指令及其数据，比如:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e30: 55 push %rbp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e31: 48 89 e5 mov %rsp,%rbp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e34: 53 push %rbx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e35: 48 83 ec 48 sub $0x48,%rsp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e39: c6 45 e0 08 movb $0x8,-0x20(%rbp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e3d: c6 45 e1 31 movb $0x31,-0x1f(%rbp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e41: c6 45 e2 10 movb $0x10,-0x1e(%rbp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e45: c6 45 e3 02 movb $0x2,-0x1d(%rbp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e49: c6 45 e4 1a movb $0x1a,-0x1c(%rbp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e4d: c6 45 e5 02 movb $0x2,-0x1b(%rbp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e51: c6 45 e6 7b movb $0x7b,-0x1a(%rbp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e55: c6 45 e7 7d movb $0x7d,-0x19(%rbp)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e59: 48 8d 45 b0 lea -0x50(%rbp),%rax&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e5d: 48 89 c7 mov %rax,%rdi&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e60: e8 81 67 00 00 callq 4095e6 &amp;lt;_ZN19ZoneActRunActCmdReqC1Ev&amp;gt; &amp;lt;----- 4095e6 is a relative offset by 81 67 00 00 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e65: 48 8d 4d e0 lea -0x20(%rbp),%rcx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e69: 48 8d 45 b0 lea -0x50(%rbp),%rax&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e6d: ba 08 00 00 00 mov $0x8,%edx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e72: 48 89 ce mov %rcx,%rsi&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e75: 48 89 c7 mov %rax,%rdi&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;402e78: e8 23 fd ff ff callq 402ba0 &amp;lt;_ZN6google8protobuf11MessageLite14ParseFromArrayEPKvi@plt&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们把func1整体挪了一个位置，挪到了func2， 还是原来的指令，比如 55 48 49 e5 顺序和值都和原来一样，但是按照 调用func1的方式调用 func2, 程序有的时候会崩溃，在找个例子里，程序会在&lt;/p&gt;</description></item><item><title>网络游戏同步模型</title><link>https://www.oneyearago.me/2020/11/18/game-sync-modle/</link><pubDate>Wed, 18 Nov 2020 20:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/11/18/game-sync-modle/</guid><description>&lt;h2 id="常见的网络游戏同步模型"&gt;常见的网络游戏同步模型&lt;/h2&gt;&#10;&lt;p&gt;何为网络同步，通俗点讲，就是在一个网络游戏里有玩家A和B同框，当A释放了一个技能，状态发生了变化，B又是如何及时表现A的当前状态的呢，就是通过网络同步技术。&lt;br&gt;&#10;不同的同步模型，目的都是为了保持每个客户端的状态一致，而一般客户端的初始状态是相同的，不同的同步模型采用不同的方式，其实就是在玩家有操作输入时，让所有玩家的客户端的状态仍能够保持一致。&lt;br&gt;&#10;假设客户端的某一对象的状态初始为S0，而玩家的输入为It,玩家输入后根据逻辑F产生了一个状态的变化SΔ，那么在某一时刻n的状态Sn，理论上是Sn=Sn1+SΔ,考虑到初始状态的话&lt;br&gt;&#10;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/10/sync-type.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;即为了让所有客户端的Sn一致，不同的模型采用了不同的方案。当前常见的同步方案主要有两种，即&lt;strong&gt;锁步同步(LockStep，又被称为“帧同步”)和状态同步(StateSync)&lt;/strong&gt;,两者的本质区别在于&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;锁步同步。上报客户端的输入It，服务器（或某台Host主机）再定期将某一时间端内(即锁步),所有玩家的It同步给每一个客户端，由客户端计算得到状态Sn。即以客户端的角度来说，上下行包都只有输入It。&lt;/li&gt;&#10;&lt;li&gt;状态同步。上报客户端的输入It和(或)Sn,服务器根据It计算得到Sn，再将Sn同步给客户端。即以客户端的角度来说，上包是输入It和（或）Sn,下行包是Sn。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;em&gt;&lt;strong&gt;确定性&lt;/strong&gt;&lt;/em&gt;，即给定相同的初始条件和相同的输入集，能够得到完全相同的结果。对于锁步同步，因为只同步了$I_n$，所以必须要保证严格地确定性，才能保证每次得到的状态相同，后续的状态才不会产生偏移。而对于状态同步，所有客户点以服务器的状态为准，所以保证关键状态与服务器下发的一致即可。客户端的不确定性包括浮点数运算、操作系统、算法、第三方库等等。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;基于实现不同，两种同步模型在某些方面的特征&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/10/diff-sync-model.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;虽然在一些方面表现出了差异，但是关于哪类游戏应该选用哪种同步模型，除了一些对某些要求比较极端的游戏类型更适合哪种模型之外(比如对实时性有着极强要求的格斗类游戏（FTG）适合使用锁步同步，而有大量玩家同时在线的MMORPG适合选用状态同步），没有严格的选用标准。一般围绕着核心玩法、安全性、开发维护成本等方面进行评估。&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;而作为一个单局PVP为主的FPS游戏，单局内可观察的网络对象较少，网络流量负担较小，而PVP要求保证公平性，以服务器的算结果作为权威,在安全性有更大优势的状态同步更为合适,且没有客户端不确定性的风险，开发负担更小，版本质量更加可控。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="二、TCP-or-UDP"&gt;二、TCP or UDP&lt;/h2&gt;&#10;&lt;p&gt;为大家所熟知的，TCP和UDP主要有以下几个特点&lt;br&gt;&#10;TCP:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;基于连接的&lt;/li&gt;&#10;&lt;li&gt;可靠保序的&lt;/li&gt;&#10;&lt;li&gt;自动分包的&lt;/li&gt;&#10;&lt;li&gt;有流量控制&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;UDP:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;无连接的&lt;/li&gt;&#10;&lt;li&gt;不保序、不可靠的&lt;/li&gt;&#10;&lt;li&gt;需要手动分包&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;一眼看上去，似乎tcp是最佳的选择，拥有我们想要的所有东西，使用起来也非常方便，但正是因为它部分强大的功能，在某些情况下，特别是在弱网情况下，会对网络的实时性会造成严重的影响。其中包括&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;TCP默认会开启Nagle算法。Nagle算法的实现是：数据只有在写缓存中累积到一定量之后，才会被发送出，通过减少需要传输的数据包数量，来优化网络，这将会造成一定的延时。虽然可以通过设置TCP_NODELAY的选项来关闭这个算法功能。&lt;/li&gt;&#10;&lt;li&gt;实现可靠保序的方式。TCP为我们提供了可靠保序的保证，但对于时效性强的数据来说，这个代价过于巨大。当一个包丢失时，接收方会无法获取后续到达的包，直到收到这个包为止（延迟到达或是重传）。对于一次丢包而言，超时重传时间大约是$2&lt;em&gt;RTT$，再加上发送到接收的$0.5&lt;/em&gt;RTT$，所以丢了一个包就会有接近3个RTT的延迟。而状态同步很多时候与其收到每一个包，收到最新的包反而是更重要的，有些包丢掉也没有关系，比如后续新的状态$S_n$会直接覆盖掉之前的状态，而包含$S_{n-1}$状态的包即使被丢掉也没有关系。&lt;/li&gt;&#10;&lt;li&gt;拥塞控制算法，包含了慢开始、拥塞避免、快速重传、快速恢复，慢启动就是连接刚建立，一点一点地提速，试探一下网络的承受能力；拥塞避免算法可以避免窗口增长过快导致窗口拥塞，而是缓慢的增加调整到网络的最佳值。两者都是为了实现更好的公共网络环境，而牺牲了一些自己的网络性能。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;以上TCP影响实时性的几点特性，除了1可通过配置避免之外，其他两个都是无法做出调整的，这导致了相对于UDP，TCP的传输速度慢很多，特别是在弱网的情况下，会大大影响游戏的体验。所以对网络实时性有要求的网络游戏，基本都采用UDP作为传输的协议，再根据需要，基于UDP开发一套可靠的协议。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="三、基于UDP开发的协议"&gt;三、基于UDP开发的协议&lt;/h2&gt;&#10;&lt;p&gt;其实选用UDP的原因只是TCP的那几个严重影响实时性的功能无法关掉而已，而TCP关于连接的概念、可靠保序的实现方式等都是值得借鉴的。下面是在UDP之上实现的一套协议&lt;/p&gt;&#10;&lt;h3 id="1-连接"&gt;1. 连接&lt;/h3&gt;&#10;&lt;p&gt;连接可以有多种状态，比如开始连接、连接中、断开连接等，通过这些状态，我们可以知道客户端和服务器的交互情况，玩家是否正常游戏。&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;连接的标识，需要在UDP的基础之上建立一个连接的概念，标识一个唯一连接的方式有很多，比如客户端请求的IP和Port。而对单局，则使用全局玩家唯一标识id作为连接的标识，单个玩家只可建立一个连接。&lt;/li&gt;&#10;&lt;li&gt;连接的建立，客户端发送建立连接的请求，服务器校验通过后，返回确认包，此时服务器认定已经成功建立连接，客户端收到Ack后确认建立了连接，如果Ack包丢了，客户端重发请求，服务器收到后再次回Ack。&lt;/li&gt;&#10;&lt;li&gt;建立的校验，服务器的连接资源是有限的，为了防止恶意连接，需要对建立连接的请求进行校验。可选用的一种简单方式是，服务器收到请求后，给客户端发一个验证包，客户端需要某种操作之后再将结果包回给服务器，连接才会建立。简单的做法是，在玩家开局之后，room就会请求pvp为每一个guid预先建立一个connection，只有拥有该connection的id才可以建立.&lt;/li&gt;&#10;&lt;li&gt;连接成功后，客户端可发送数据包，服务器在tick中处理处理每一个包并进行统计记录。&lt;/li&gt;&#10;&lt;li&gt;客户端的主动断开，客户端发出断开的请求，服务器设置标志位让出资源，并上报该链接的全局统计结果。&lt;/li&gt;&#10;&lt;li&gt;数据加密，简单的做法是对包内容的加密只是做了简单的异或处理，每个连接都会有一个密钥，将密钥与数据按照某种规则进行异或的操作。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="2-可靠性"&gt;2. 可靠性&lt;/h3&gt;&#10;&lt;h4 id="2-1-QOS"&gt;2.1 QOS&lt;/h4&gt;&#10;&lt;p&gt;根据对可靠性的不同需求，一般会实现不同可靠程度的通信通道(channel),包括&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;不可靠不保序通道&lt;/li&gt;&#10;&lt;li&gt;不可靠保序通道&lt;/li&gt;&#10;&lt;li&gt;可靠保序通道&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;只实现了&lt;strong&gt;不可靠保序和可靠保序&lt;/strong&gt;。（不可靠不保序应用价值不大？）&lt;br&gt;&#10;两者的实现都是基于数据包Package的序列号Seq实现的，每个channel记录了两个seq : 1.当前channel的从socket收到的最大的seq(last_recv_seq_)；2.当前channel已经处理的数据包的seq（recv_read_seq_）。在上层从调用接口（ReadData）读取缓冲区数据时，两者的处理方式不同&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;不可靠保序，读取缓冲区内从recv_read_seq_到last_recv_seq_的所有数据包，如果包未到达则算丢失，不重传。&lt;/li&gt;&#10;&lt;li&gt;可靠保序，只读取recv_read_seq_的包，如果该包未到达，则直接返回，直到该包到达为止，具有超时重传和快速重传策略。&lt;/li&gt;&#10;&lt;li&gt;如果实现不可靠不保序的话，应该直接读取当前缓冲区内所有的包，再根据需要制定判定丢失的规则，不重传。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h4 id="2-2-重传机制"&gt;2.2 重传机制&lt;/h4&gt;&#10;&lt;p&gt;重传的策略有两个，超时重传+快速重传。&lt;br&gt;&#10;数据包触发重传的条件有两个:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;到达超时时间，即 Now &amp;lt; send_time + RTO ，其中RTO的参考计算公式为&lt;br&gt;&#10;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/10/game-sync-tcp-rudp.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;DevRTT是Deviation RTT。在Linux下，α = 0.125，β = 0.25， μ = 1，∂= 4 。一般为了防止极端情况下rto过大或过小，可以对rto的值进行”钳位“。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;比此包更新的数据包已被确认，且此包发送次数小于等于1时（即最多发过1次），也会立刻触发一次重发。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;</description></item><item><title>画画</title><link>https://www.oneyearago.me/2020/11/11/painting/</link><pubDate>Wed, 11 Nov 2020 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/11/11/painting/</guid><description>&lt;p&gt;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/10/k-pthread.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/10/k-memory.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/10/k-mutex.png" alt="image"&gt;&lt;/p&gt;</description></item><item><title>程序的机器级表示</title><link>https://www.oneyearago.me/2020/11/03/csapp-3-asm/</link><pubDate>Tue, 03 Nov 2020 21:33:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/11/03/csapp-3-asm/</guid><description>&lt;h1 id="CSAPP"&gt;CSAPP&lt;/h1&gt;&#10;&lt;p&gt;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/function_stack/csapp-3-7.svg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h2 id="机器级表示"&gt;机器级表示&lt;/h2&gt;&#10;&lt;h3 id="汇编"&gt;汇编&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;两个抽象&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;硬件 指令集 ISA 的抽象&lt;/li&gt;&#10;&lt;li&gt;虚拟内存 大数组&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;汇编与高级语言的区别&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;暴露的可操作&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;程序计数器 rip&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;条件寄存器&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;整数寄存器&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/function_stack/function_call_register" alt=""&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;ATT 汇编与 intel 汇编区别&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;intel 省略了 指示大小的后缀&lt;/li&gt;&#10;&lt;li&gt;Intel省略了 寄存器前的 %&lt;/li&gt;&#10;&lt;li&gt;Intel 有很多不同的方法 描述内存的位置&lt;/li&gt;&#10;&lt;li&gt;多个操作数 Intel 列出的操作数 是相反的&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="访问信息"&gt;访问信息&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;不同类型操作数&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;寄存器&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;立即数&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;内存引用&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;表&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/function_stack/function_call_op" alt=""&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;数据传送&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;mov S , D&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;入栈出栈&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;push&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;sub %rsp; mov xxx (%rsp)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;pop&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;mov %rsp xxx , add %rsp&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;pic&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://blog2020-1251969284.cos.ap-shanghai.myqcloud.com/function_stack/function_call_stack" alt=""&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;lea&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;load effective addr&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;加载有效地址&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;lea s , d -&amp;gt; d=&amp;amp;s&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>多进程游戏压测工具</title><link>https://www.oneyearago.me/2020/10/02/press_robots/</link><pubDate>Fri, 02 Oct 2020 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/10/02/press_robots/</guid><description>&lt;h3 id="概述"&gt;概述&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://202003-1251969284.cos.ap-shanghai.myqcloud.com/game_c2s.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;首先对于游戏的业务，一般是玩家登陆到大厅，有一些任务、物品、好友、排行榜、聊天这种交互，其次是玩家与玩家之前的匹配与对局。以Moba游戏为例，玩家主要的行为就是登陆后进行匹配，匹配到水平差不多的10个人，分为两队，每组5个人创建对局进行pvp战斗，玩家的操作以指令的方式由客户端发到服务器。 大厅中客户端与服务器的连接是TCP连接，对局中玩家的操作更关注实时性，一般的用可靠UDP进行通信。&lt;/p&gt;&#10;&lt;h5 id="接入层"&gt;接入层&lt;/h5&gt;&#10;&lt;p&gt;对于大厂而言，客户端接入服务器一般是连接一个接入层，经接入层转发请求到游戏服务器，接入层的目的也是标准化（工业化）的一部分，使得每个游戏都可以复用，只需要写业务逻辑从而不用重写网络连接的代码。我们模拟玩家的行为，也就是往游戏服务器对应的接入层发包。&lt;/p&gt;&#10;&lt;h5 id="内存池缓存数据"&gt;内存池缓存数据&lt;/h5&gt;&#10;&lt;p&gt;对于游戏，玩家身上有很多属性，比如各种物品、分数、赛季信息、抽奖信息、VIP等级、头像框id、注册时间、各种活动的数据等。而游戏在开服或者有一些活动的时候，也是玩家集中登陆集中操作的时候，服务器的做法一般是在玩家登陆的服务器 建立内存池，将玩家的数据缓存到内存，纯内存的操作处理玩家数据比较高效。&lt;/p&gt;&#10;&lt;h5 id="通信协议"&gt;通信协议&lt;/h5&gt;&#10;&lt;p&gt;王者荣耀和吃鸡应该也是使用Protocol Buffers&lt;br&gt;&#10;&lt;a href="https://developers.google.com/protocol-buffers"&gt;https://developers.google.com/protocol-buffers&lt;/a&gt;&lt;/p&gt;&#10;&lt;h4 id="服务器逻辑"&gt;服务器逻辑&lt;/h4&gt;&#10;&lt;p&gt;服务器主循环：&lt;br&gt;&#10;&lt;img src="https://202003-1251969284.cos.ap-shanghai.myqcloud.com/tapp_svr_loop.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="压测概述与思路"&gt;压测概述与思路&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://202003-1251969284.cos.ap-shanghai.myqcloud.com/gameperf.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;我们通过模拟真实玩家发起压力测试，有的场景比较简单，比如查询排行榜，只要构造了排行榜的数据，发起查询请求即可；但是也有比较复杂的场景，比如巅峰赛观战，比如需要8000人在对局，与此同时，有40000人在观战，正在对局中的人需要模拟玩家的行为，移动、一技能、装备、聊天、弹幕等操作，此时我们需要保持着和多个进程的连接。&lt;/p&gt;&#10;&lt;p&gt;同时，每个进程保持的连接可能是有限的，比如接入层sdk的限制，连接层和逻辑层需要做到可扩展。&lt;/p&gt;&#10;&lt;h4 id="共享内存通道"&gt;共享内存通道&lt;/h4&gt;&#10;&lt;p&gt;为支持逻辑层和连接层的可扩展，每增加一个逻辑层或者连接层，可以增加一个共享内存通道，&lt;br&gt;&#10;&lt;img src="https://202003-1251969284.cos.ap-shanghai.myqcloud.com/bus_channel.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;详细的代码在这里，可编译运行：&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/changan29/playcpp/tree/master/bus_channel"&gt;https://github.com/changan29/playcpp/tree/master/bus_channel&lt;/a&gt;&lt;/p&gt;&#10;&lt;h4 id="定时器"&gt;定时器&lt;/h4&gt;&#10;&lt;p&gt;一个Timer的实现需要具备以下几个行为:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;StartTimer(Interval, ExpiryAction)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;注册一个时间间隔为 Interval 后执行 ExpiryAction 的定时器实例，其中，返回 TimerId 以区分在定时器系统中的其他定时器实例。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;StopTimer(TimerId)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;根据 TimerId 找到注册的定时器实例并执行 Stop 。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;PerTickBookkeeping()&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;在一个 Tick 时间粒度内，定时器系统需要执行的动作，它最主要的行为，就是检查定时器系统中，是否有定时器实例已经到期。&lt;/p&gt;&#10;&lt;p&gt;具体的代码实现思路就是：在StartTimer的时候，把 当前时间 + Interval 作为key放入一个容器，然后在Loop的每次Tick里，从容器里面选出一个最小的key与当前时间比较，如果key小于当前时间，则这个key代表的timer就是expired，需要执行它的ExpiryAction(一般为回调)。&lt;/p&gt;&#10;&lt;h5 id="链表的实现"&gt;链表的实现&lt;/h5&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;精度是 1ms&lt;/li&gt;&#10;&lt;li&gt;最长时间是10min,延长时间可以增加 slot数量,slot时间的间隔是 1ms&lt;/li&gt;&#10;&lt;li&gt;通过继承Timer父类，在子类重写timeout实现 超时回调&lt;/li&gt;&#10;&lt;li&gt;每次都需要遍历超过时间的所有链表，时间复杂度为O(n)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;详细的代码在这里，可编译运行：&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/changan29/codeLib/tree/master/timer"&gt;https://github.com/changan29/codeLib/tree/master/timer&lt;/a&gt;&lt;/p&gt;&#10;&lt;h4 id="协程"&gt;协程&lt;/h4&gt;&#10;&lt;p&gt;协程框架使用的是 ucontext，参考之前写的这篇：&lt;br&gt;&#10;&lt;a href="http://www.oneyearago.me/2020/05/26/ucontext_01/"&gt;http://www.oneyearago.me/2020/05/26/ucontext_01/&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;实现的机制是：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;每个玩家抽象成机器人，有一个Robot类，Robot类继承 协程类&lt;/li&gt;&#10;&lt;li&gt;每个Robot有自己的行为，logic进程的代码就是robot的行为&lt;/li&gt;&#10;&lt;li&gt;把每个玩家抽象成一个协程，方便写逻辑，相互独立，等待调度&lt;/li&gt;&#10;&lt;li&gt;初始化的时候创建一批玩家（协程），按照配置等待一定的时间开始执行（比如10ms）,结合定时器，可以按照固定的频率发包（比如1秒发送200个）&lt;/li&gt;&#10;&lt;li&gt;调度程序 调度程序比较简单，比如有了响应包或者定时器事件到了，根据timer_id或者pkg_head_src标记等 resume 协程&lt;/li&gt;&#10;&lt;/ol&gt;</description></item><item><title>hook的几种方式及原理学习</title><link>https://www.oneyearago.me/2020/09/27/hook_and_internal/</link><pubDate>Sun, 27 Sep 2020 01:17:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/09/27/hook_and_internal/</guid><description>&lt;h3 id="概述"&gt;概述&lt;/h3&gt;&#10;&lt;p&gt;对于大型的工程项目，依赖许多人的配合，包含大量不同的代码库与服务，有的我们能够访问程序的源代码，有的可以访问程序的可重定位文件，有的可以访问到可执行文件及其环境，假如我们想在在不同的层面改变或者添加一些逻辑，操作系统、编译器以及程序语言、代码库等都提供了 一些机制使得 开发者可以 方便的 增加或替换代码逻辑，对于逻辑调试、测试、性能分析、版本兼容等都有比较好的效果。&lt;/p&gt;&#10;&lt;h3 id="编译器支持"&gt;编译器支持&lt;/h3&gt;&#10;&lt;h4 id="Function-Attribute"&gt;Function Attribute&lt;/h4&gt;&#10;&lt;p&gt;GNU C 使用attribute 可以设置函数属性（Function Attribute ）、变量属性（Variable Attribute ）和类型属性（Type Attribute ）。 &lt;strong&gt;attribute&lt;/strong&gt;前后都有两个下划线，并且后面会紧跟一对原括弧，括弧里面是相应的&lt;strong&gt;attribute&lt;/strong&gt;参数。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;attribute&lt;/strong&gt;语法格式为：&lt;strong&gt;attribute&lt;/strong&gt; ( ( attribute-list ) )&lt;/p&gt;&#10;&lt;p&gt;比如常用的constructor属性，则会使函数在main（）函数执行之前被自动的执行。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;stdio.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;stdlib.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;static int * g_count = NULL;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;__attribute__((constructor)) void load_file()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;Constructor is called.\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; g_count = (int *)malloc(sizeof(int));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (g_count == NULL)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fprintf(stderr, &amp;#34;Failed to malloc memory.\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;__attribute__((destructor)) void unload_file()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;destructor is called.\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (g_count)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; free(g_count);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int main()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;参考: &lt;a href="https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html"&gt;https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html&lt;/a&gt;&lt;/p&gt;</description></item><item><title>去看天问一号发射</title><link>https://www.oneyearago.me/2020/07/23/tianwen-1-wenchang/</link><pubDate>Thu, 23 Jul 2020 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/07/23/tianwen-1-wenchang/</guid><description>&lt;h3 id="天问一号发射"&gt;天问一号发射&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianwen-up.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianwen-static.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;h3 id="卫星发射时间表"&gt;卫星发射时间表&lt;/h3&gt;&#10;&lt;p&gt;时间表在网上有很多,善用搜索基本找是没什么问题.微博上关注了一个圈内的小姐姐,更新时间和信息都比较及时.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;论坛&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="http://www.spaceflightfans.cn/china-launch-schedule"&gt;http://www.spaceflightfans.cn/china-launch-schedule&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/schedule.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;微博&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://weibo.com/linxiaoyi2530?is_hot=1"&gt;https://weibo.com/linxiaoyi2530?is_hot=1&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="文昌卫星发射中心和文昌、龙楼镇"&gt;文昌卫星发射中心和文昌、龙楼镇&lt;/h3&gt;&#10;&lt;p&gt;​ 地理位置上的发射点如下，海口机场对应的高铁站是美兰站，可以换乘高铁到文昌站，高铁约半个小时，文昌这个市最近好像把市政府搬到了郊区，地图上可以看到有明显的老城区和新城区，如果龙楼镇上找不到住的地方，建议住到文昌老城区，第二天早上或者择机过去需要 35 - 40分钟左右。提前一周没订到酒店，过去找一般的酒店基本不太可能，过去问了6家酒店，都没有房间了，网吧的包间也没有了 😂&lt;/p&gt;&#10;&lt;p&gt;文昌高铁站即火车站，在老城区，下车了比较想县城的火车站，比较好打车，有很多拉客的，大概80 - 100块钱到龙楼。&lt;/p&gt;&#10;&lt;p&gt;住的地方看时间和季节而异,假如是自己一个人,当然是住酒店更省心一些;如果带了帐篷和厚衣服,住在海边也是可以的,人多的话更有氛围,即使是热带,晚上海边也有可能比较冷,需要带厚衣服.&lt;/p&gt;&#10;&lt;p&gt;有钱还是希尔顿 2333 …&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/hainan-wenchangn-longlou.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/xihaishan.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/wenchangzhan.jpeg" alt="文昌站"&gt;&lt;/p&gt;&#10;&lt;h3 id="封路"&gt;封路&lt;/h3&gt;&#10;&lt;p&gt;​ 一、实施管制时间和范围。2020年7月23日0时至23日24时 ，禁止运载易燃易爆危化品运输车、核载5吨以上货运车辆进入文昌市管制路段，管制路段禁止停放一切车辆；火箭发射前3小时，禁止无通行证的车辆进入文昌市东龙线、龙昌线、文铜线和乌东线；火箭发射前1小时，禁止一切车辆进入文昌市东龙线、龙昌线、文铜线和乌东线。&lt;br&gt;&#10;解除管制时间根据道路通行情况确定。&lt;br&gt;&#10;​ 二、实施管制路段。文昌市东龙线、龙昌线、文铜线、乌东线（3.3km至6.3km）；龙楼钻石大道、淇水湾大道、紫薇路、彩虹南北路、清澜大桥。&lt;br&gt;&#10;管制期间，请车辆驾驶人、行人严格按照道路交通标志标线、信号灯和执勤交通警察的指挥通行。&lt;br&gt;&#10;因实施交通管制造成的不便，敬请谅解。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianwen-road.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;h3 id="最佳观测点"&gt;最佳观测点&lt;/h3&gt;&#10;&lt;p&gt;​ 最佳观看点大多集中在龙楼镇，分别是文昌清澜大桥西侧桥头公园、龙楼交界处&lt;a href="http://tech.qq.com/all/twht.htm"&gt;航天&lt;/a&gt;科普中心、龙楼镇天福云龙湾度假村、龙楼镇山海天酒店营销中心西侧、龙楼镇山海天酒店沿海沙滩、龙楼镇堂洪港（石头公园西侧）、龙楼镇213地块（石头公园西侧）、龙楼镇希尔顿酒店沿海沙滩。&lt;/p&gt;&#10;&lt;p&gt;​ 海南省政府官网有资料，不过是很早以前的，我是去的希尔顿前面的海滩。&lt;/p&gt;&#10;&lt;h3 id="这大海有点漂亮"&gt;这大海有点漂亮&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianwen-sea.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-far-1.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;h3 id="普通手机拍出来的效果"&gt;普通手机拍出来的效果&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-fly-1.mp4"&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-fly-1.mp4" alt=" "&gt;&lt;/a&gt;&#10;&lt;a href="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-fly-2.mp4"&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-fly-2.mp4" alt=" "&gt;&lt;/a&gt;&#10;&lt;a href="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-fly-3.mp4"&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-fly-3.mp4" alt=" "&gt;&lt;/a&gt;&#10;&lt;a href="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-fly-4.mp4"&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-fly-4.mp4" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;h3 id="遇到的追星的人"&gt;遇到的追星的人&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianen-child.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianwen-penple.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianwen-penple-2.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianwen-penple-3.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;h3 id="找队友"&gt;找队友&lt;/h3&gt;&#10;&lt;p&gt;​ 打算11月12月过去骑行东线，求队友🚴‍♀️&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://work-2020-1251969284.cos.ap-nanjing.myqcloud.com/tianwen-ride.jpeg" alt=""&gt;&lt;/p&gt;&#10;&lt;h3 id="出门-list"&gt;出门 list&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;身份证&lt;/li&gt;&#10;&lt;li&gt;钱包&lt;/li&gt;&#10;&lt;li&gt;至少一件厚衣服&lt;/li&gt;&#10;&lt;li&gt;充电器,充电宝&lt;/li&gt;&#10;&lt;li&gt;数据线 USB * 2&lt;/li&gt;&#10;&lt;li&gt;电脑,电脑充电器&lt;/li&gt;&#10;&lt;li&gt;剃须刀&lt;/li&gt;&#10;&lt;li&gt;钥匙&lt;/li&gt;&#10;&lt;li&gt;工卡. token&lt;/li&gt;&#10;&lt;li&gt;口罩&lt;/li&gt;&#10;&lt;li&gt;账号 密码&lt;/li&gt;&#10;&lt;li&gt;塑料袋&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>Linux中的段</title><link>https://www.oneyearago.me/2020/06/26/linux-segment/</link><pubDate>Fri, 26 Jun 2020 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/06/26/linux-segment/</guid><description>&lt;h3 id="Linux中的段"&gt;Linux中的段&lt;/h3&gt;&#10;&lt;p&gt;Intel 微处理器的段机制是从8086 开始提出的， 那时引入的段机制解决了从CPU 内部&lt;br&gt;&#10;16 位地址到20 位实地址的转换。为了保持这种兼容性，386 仍然使用段机制，但比以前复杂。&lt;br&gt;&#10;因此，Linux 内核的设计并没有全部采用Intel 所提供的段方案，仅仅有限度地使用&lt;br&gt;&#10;了一下分段机制。这不仅简化了Linux 内核的设计，而且为把Linux 移植到其他平台创造了&lt;br&gt;&#10;条件，因为很多RISC 处理器并不支持段机制。但是，对段机制相关知识的了解是进入Linux&lt;br&gt;&#10;内核的必经之路。&lt;/p&gt;&#10;&lt;p&gt;从2.2 版开始，Linux 让所有的进程（或叫任务）都使用相同的逻辑地址空间，因此就&lt;br&gt;&#10;没有必要使用局部描述符表LDT。但内核中也用到LDT，那只是在VM86 模式中运行Wine 时，&lt;br&gt;&#10;即在Linux 上模拟运行Windows 软件或DOS 软件的程序时才使用。&lt;/p&gt;&#10;&lt;h4 id="linux的GDT"&gt;linux的GDT&lt;/h4&gt;&#10;&lt;p&gt;Linux 在启动的过程中设置了段寄存器的值和全局描述符表GDT 的内容，段的定义在include/asm-i386/segment.h 中：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#define __KERNEL_CS 0x10 /* 内核代码段, index=2,TI=0,RPL=0 */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#define __KERNEL_DS 0x18 /* 内核数据段, index=3,TI=0,RPL=0 */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#define __USER_CS 0x23 /* 用户代码段, index=4,TI=0,RPL=3 */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#define __USER_DS 0x2B /* 用户数据段, index=5,TI=0,RPL=3 */&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;从定义看出，没有定义堆栈段，实际上，Linux 内核不区分数据段和堆栈段，这也体现&lt;br&gt;&#10;了Linux 内核尽量减少段的使用。因为没有使用LDT，因此，TI=0，并把这4 个段都放在GDT&lt;br&gt;&#10;中, index 就是某个段在GDT 表中的下标。内核代码段和数据段具有最高特权，因此其RPL&lt;br&gt;&#10;为0，而用户代码段和数据段具有最低特权，因此其RPL 为3。可以看出，Linux 内核再次简&lt;br&gt;&#10;化了特权级的使用，使用了两个特权级而不是4 个。&lt;/p&gt;</description></item><item><title>附近的人</title><link>https://www.oneyearago.me/2020/06/22/geohash/</link><pubDate>Mon, 22 Jun 2020 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/06/22/geohash/</guid><description>&lt;h3 id="why"&gt;why&lt;/h3&gt;&#10;&lt;p&gt;位置信息在软件以及显示生活中有了越来越多的应用，比如微信里面附近的人，很多游戏里面也加入了地理位置的玩法，让玩家与玩家有更多的互动，提高黏性。&lt;/p&gt;&#10;&lt;p&gt;假想这样一个需求：玩家获取附近的人，一起开黑或者某些社交软件获取附近的人交友或者交流，需要怎么实现，假定该软件的使用者比较多。&lt;/p&gt;&#10;&lt;p&gt;本身学过一些硬件、传感器的东西，想从计算机硬件和软件的角度写一下自己的理解，首先我们有以下几个问题：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;我们如何知道自己在世界上的位置&lt;/li&gt;&#10;&lt;li&gt;如何快速的找到附近的人（附近的旅店）&lt;/li&gt;&#10;&lt;li&gt;计算机如何处理地球上的位置数据最高效&lt;/li&gt;&#10;&lt;li&gt;geohash及其使用&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="我们如何知道自己在世界上的位置"&gt;我们如何知道自己在世界上的位置&lt;/h3&gt;&#10;&lt;p&gt;面对这个问题的时候，我的第一想法是 手机的GPS模块，手机是一个相对精密的系统，里面当然也包括各种传感器，GPS模块当然也包括，那么我们通过GPS模块就可以拿到我们的位置信息啊，不就是那么简单吗。转念一想，GPS这么小一个东西是怎么知道自己的位置的？&lt;/p&gt;&#10;&lt;p&gt;不识庐山真面目，只缘身在此山中。GPS传感器遍布在地球的各个角落，要看到自己的位置，目光肯定要在自己环境之外，要获取在地球的相对位置，需要在地球之外找到合适的角度:&lt;br&gt;&#10;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/lbs_gps" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;于是，通过地球之外的卫星系统，我们可以通过两个简单的数字来量化自身的位置：(经度，纬度)。&lt;/p&gt;&#10;&lt;p&gt;比如可行科学园的位置: (113.942016,22.542644)&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/lbs_gps_kexing.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="附近的人需要的前置基础设施有哪些"&gt;附近的人需要的前置基础设施有哪些&lt;/h3&gt;&#10;&lt;p&gt;我们有了坐标去刻画位置，那么我要找我附近3km的餐馆，需要怎么找？&lt;/p&gt;&#10;&lt;p&gt;一个简单的方法如下，根据坐标，计算世界上所有餐馆到我的位置的距离，取其中距离小于3000m的。理论上可以实现，但是方法未免太傻逼了，我找我附近的，还要遍历其他地方的，极其不优雅。&lt;/p&gt;&#10;&lt;p&gt;在找到如何实现怎么查找更高效之前，我自己考虑了一下，可以根据GPS 的数据过滤临近的GPS，比如23*开头的附近应该也是 22、24这种，这样找就缩小到一个比较小的区域，但是拿两个制约参数找毕竟不如一个制约参数快，于是又更有效的方式。&lt;/p&gt;&#10;&lt;h4 id="geohash：经纬度的一维状态"&gt;geohash：经纬度的一维状态&lt;/h4&gt;&#10;&lt;p&gt;GeoHash是一种对地理坐标进行编码的方法，它将二维坐标映射为一个字符串。每个字符串代表一个特定的矩形，在该矩形范围内的所有坐标都共用这个字符串。字符串越长精度越高，对应的矩形范围越小。&lt;/p&gt;&#10;&lt;p&gt;其实现的思路是这样的，要想把由二维元素表述的位置转变成一维的，并且，相同坐标的点对应的字符串前缀是一样的，那么我去寻找相同位置的时候，可以寻找相同前缀的字符串，字符串的位数也就是位置的精度。由于是一维的表述，方便构成 key: value的索引，便于查找和缓存，效率的提高了至少一个数量级。&lt;/p&gt;&#10;&lt;p&gt;对一个地理坐标编码时，按照初始区间范围纬度[-90,90]和经度[-180,180]，计算目标经度和纬度分别落在左区间还是右区间。落在左区间则取0，右区间则取1。然后，对上一步得到的区间继续按照此方法对半查找，得到下一位二进制编码。当编码长度达到业务的进度需求后，根据“偶数位放经度，奇数位放纬度”的规则，将得到的二进制编码穿插组合，得到一个新的二进制串。最后，根据base32的对照表，将二进制串翻译成字符串，即得到地理坐标对应的目标GeoHash字符串。&lt;/p&gt;&#10;&lt;p&gt;总的来说，geohash是经纬度的一维形式，代表一个坐标。&lt;/p&gt;&#10;&lt;h5 id="根据经纬度计算GeoHash二进制编码"&gt;根据经纬度计算GeoHash二进制编码&lt;/h5&gt;&#10;&lt;p&gt;地球纬度区间是[-90,90]， 科兴的纬度是22.542644，可以通过下面算法对纬度39.928167进行逼近编码:&lt;/p&gt;&#10;&lt;p&gt;1）区间[-90,90]进行二分为[-90,0),[0,90]，称为左右区间，可以确定39.928167属于右区间[0,90]，给标记为1；&lt;/p&gt;&#10;&lt;p&gt;2）接着将区间[0,90]进行二分为 [0,45),[45,90]，可以确定39.928167属于左区间 [0,45)，给标记为0；&lt;/p&gt;&#10;&lt;p&gt;3）递归上述过程39.928167总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小，并越来越逼近39.928167；&lt;/p&gt;&#10;&lt;p&gt;4）如果给定的纬度x（22.542644）属于左区间，则记录0，如果属于右区间则记录1，这样随着算法的进行会产生一个序列1011100，序列的长度跟给定的区间划分次数有关。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/lbs_ws10.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;我们按照上面的方法，将位置的GPS转化为一组二进制，经纬度各是一组1010101的串，偶数位放经度，奇数位放纬度，把2串编码组合生成新串。将字符串进行 base32编码就可以得到 ws103这样的 位置代码。&lt;/p&gt;&#10;&lt;h5 id="GeoHash-Base32编码长度与精度"&gt;GeoHash Base32编码长度与精度&lt;/h5&gt;&#10;&lt;p&gt;当geohash base32编码长度为8时，精度在19米左右，而当编码长度为9时，精度在2米左右，编码长度需要根据数据情况进行选择。&lt;/p&gt;&#10;&lt;p&gt;可以在类似如下可视化平台查看某个地区的 GeoHash&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="http://geohash.cn/"&gt;http://geohash.cn/&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;genhash编码的解析贴一个维基百科的例子：&lt;br&gt;&#10;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/lbs_genhash_code.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="降维与Z阶曲线"&gt;降维与Z阶曲线&lt;/h3&gt;&#10;&lt;p&gt;从经纬度到geohash值,我们经历了一个降维的过程，我们知道了如何从一个经纬度去计算一个geohash，那么，为什么可以这么计算呢？&lt;/p&gt;&#10;&lt;p&gt;计算机上有一个分形的分支，数学上也有，同时，数学中有一类被称为降维函数的存在，将多维数据映射到一维，同时保留数据点的局部性。&lt;/p&gt;&#10;&lt;p&gt;参考： &lt;a href="https://en.wikipedia.org/wiki/Z-order_curve"&gt;Z阶曲线&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;比如经纬度降维的过程可以如下证明:&lt;br&gt;&#10;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/lbs_genhash_Z-order_curve.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;数学基础收到冲击 : ）&lt;/p&gt;&#10;&lt;h3&gt;&lt;/h3&gt;&#10;&lt;p&gt;论点:现在的很多玩法很多都是基于几十年前别人的研究项目,想起来之前看到的一句话,你能看到多远的过去,就能看到多远的将来.&lt;/p&gt;&#10;&lt;h3 id="参考"&gt;参考&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://www.cnblogs.com/woshimrf/p/geohash-visualization.html"&gt;https://www.cnblogs.com/woshimrf/p/geohash-visualization.html&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.cnblogs.com/LBSer/p/3310455.html"&gt;https://www.cnblogs.com/LBSer/p/3310455.html&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Geohash"&gt;https://en.wikipedia.org/wiki/Geohash&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Z-order_curve"&gt;https://en.wikipedia.org/wiki/Z-order_curve&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>ucontext</title><link>https://www.oneyearago.me/2020/05/26/ucontext_01/</link><pubDate>Tue, 26 May 2020 22:31:45 +0800</pubDate><guid>https://www.oneyearago.me/2020/05/26/ucontext_01/</guid><description>&lt;h3 id="概述"&gt;概述&lt;/h3&gt;&#10;&lt;p&gt;ucontext系列允许在C中实现高级控制流模式，例如迭代器，纤维和协程。&lt;/p&gt;&#10;&lt;h3 id="主要的4个函数"&gt;主要的4个函数&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;int setcontext(const ucontext_t *ucp)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;This function transfers control to the context in ucp. Execution continues from the point at which the context was stored in ucp. setcontext does not return.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;int getcontext(ucontext_t *ucp)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Saves current context into ucp. This function returns in two possible cases: after the initial call, or when a thread switches to the context in ucp via setcontext or swapcontext. The getcontext function does not provide a return value to distinguish the cases (its return value is used solely to signal error), so the programmer must use an explicit flag variable, which must not be a register variable and must be declared volatile to avoid constant propagation or other compiler optimisations.&lt;/p&gt;</description></item><item><title>程序员的自我修养 - 系统调用及原理</title><link>https://www.oneyearago.me/2020/02/16/syscall_and_innner/</link><pubDate>Sun, 16 Feb 2020 20:29:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/02/16/syscall_and_innner/</guid><description>&lt;h3 id="什么是系统调用"&gt;什么是系统调用&lt;/h3&gt;&#10;&lt;p&gt;系统调用是应用程序(包含运行库)与操作系统内核的接口，它决定了应用程序如何与内核打交道。在现在的操作系统系统里，程序运行的时候，本身是没有权利访问系统的资源，由于系统有限的资源有可能被不同的应用程序同时访问，因此，如果不加以保护，各个应用程序的冲突在所难免。所以现代操作系统都尽可能的把冲突的资源保护起来，阻止程序直接访问。这些资源，包括文件、网络、IO、各种设备等。&lt;/p&gt;&#10;&lt;p&gt;为了让应用程序有能力访问系统资源，每个操作系统都会提供一套接口，以供应用程序使用。这些接口往往通过终端的形式实现，比如Linux使用0x80号中断作为系统调用的入口，Windows采用0x2E号中断作为系统调用的入口。&lt;/p&gt;&#10;&lt;h4 id="Linux系统调用"&gt;Linux系统调用&lt;/h4&gt;&#10;&lt;p&gt;在x86下，Linux的系统调用由0x80完成，各个通用寄存器用于传递参数，EAX寄存器用于表示系统调用的接口号，比如EAX=2表示创建进程(fork)，每个系统调用都对应于内核源码中的一个函数，他们都以sys_开头。&lt;/p&gt;&#10;&lt;h3 id="系统调用原理"&gt;系统调用原理&lt;/h3&gt;&#10;&lt;p&gt;现代的CPU常常可以在多种不同的特权级下执行命令，在现在的操作系统中，通常有两种特权级，分为用户模式和内核模式，也称用户态和内核态。由于多种模式的存在，操作系统可以让不同的代码运行在不同的模式上，提高稳定性和安全性。&lt;/p&gt;&#10;&lt;p&gt;系统调用时运行在内核态的，而应用程序一般是运行在用户态。用户态的程序如何运行内核态的代码呢？操作系统一般是通过中断来从用户态切换到内核态。中断是一个硬件或者软件发出请求，请求CPU暂停当前的工作去处理更加重要的事情。&lt;/p&gt;&#10;&lt;p&gt;中断一般具有两个属性，一个称为中断号，一个称为中断处理程序(Interrupt Service Routine，ISR)。不同的中断具有不同的中断号，而同时一个中断处理程序一一对应一个中断号。在内核中，有一个数组称为中断向量表（interrupt vector table),这个数组的第n项包含了指向第n号中断处理程序的指针。当中断到来时，CPU回暂停执行当前的行的代码，根据中断的中断号，在中断向量表中找到对应的中断处理程序，并调用它。&lt;/p&gt;&#10;&lt;p&gt;通常意义上，中断有两种类型，一种称为硬件中断，这种中断来自硬件的异常或者事件的发生，入电源掉电、键盘被按下等。另外一种称为软件中断，软件中断通常是一条指令(i386下是int),带有一个参数记录中断号。和中断一样，系统调用都有一个系统调用号，表明是哪一个系统调用，这个系统调用号通常就是系统调用在系统调用表中的位置，这个系统调用号在执行int之前会被放置到某个固定的寄存器里面。比如Linux的int 0x80，系统调用号是放在eax，然后使用int 0x80调用中断，从eax取得系统调用号。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/linux_syscall_0x80.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="切换堆栈"&gt;切换堆栈&lt;/h4&gt;&#10;&lt;p&gt;在实际执行中断向量表中的第0x80号元素所对应的函数之前，CPU首先还要进行栈的切换。在Linux中，用户态和内核态使用不同的栈，两者各自负责各自的函数调用，互不干扰。但是在程序调用 0x80号中断时，程序的执行流程从用户态切换到内核态，这时候程序的当前栈必须也从用户栈切换到内核栈。返回时同理。&lt;/p&gt;&#10;&lt;p&gt;所谓的当前栈，指的是ESP的值所在的栈空间，如果ESP的值位于用户栈的范围内，那么当前程序的栈就是用户栈，反之亦然，此外，寄存器SS的值还应该指向当前栈所在的页。所以，将当前栈由用户栈切换到内核栈的实际行为就是:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;保存当前ESP，SS的值&lt;/li&gt;&#10;&lt;li&gt;将ESP，SS的值设置成内核栈的值&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;当0x80寄存器发生的时候，CPU除了进入内核态以外，还会自动完成以下几件事：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;找到当前进程的内核栈（每个进程都有自己的内核栈）&lt;/li&gt;&#10;&lt;li&gt;在内核栈中依次压入用户态的寄存器SS,ESP,EFLAGS,CS,EIP&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;返回时用这些地址和数据返回。&lt;/p&gt;&#10;&lt;h3 id="Linux新型系统调用机制"&gt;Linux新型系统调用机制&lt;/h3&gt;&#10;&lt;p&gt;由于基于int指令的系统调用在奔腾4处理器上性能不佳，Linux2.5版本开始支持一种新的系统调用机制。这种机制使用奔腾2带开始提供的一组专门针对系统调用的指令-syscenter/sysexit。&lt;/p&gt;&#10;&lt;p&gt;调用syscenter之后，系统会直接跳转到由某个寄存器制定的函数执行，并自动完成特定特权级的转换，堆栈切换等功能。&lt;/p&gt;&#10;&lt;p&gt;在参数传递方面，新型的系统调用和int 的系统调用完全相同，仍然使用EBX..EDX,ESI，EDI，EBP这六个寄存器。&lt;/p&gt;&#10;&lt;p&gt;分析过程：&lt;br&gt;&#10;ldd获取共享库，找到linux-gate.so.1，也就是地址0xffffe0000被映射到vdso，可以把文件的这个段单独导出到一个文件里分析，vdso导出一系列函数，其中 _kernel_vsyscall_ 中使用了 syscenter系统调用，从而完成系统调用。&lt;/p&gt;</description></item><item><title>程序员的自我修养 - 可执行文件的装载与进程</title><link>https://www.oneyearago.me/2020/02/15/loader_and_start_process/</link><pubDate>Sat, 15 Feb 2020 17:58:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/02/15/loader_and_start_process/</guid><description>&lt;h3 id="进程的建立"&gt;进程的建立&lt;/h3&gt;&#10;&lt;p&gt;从操作系统的角度看，一个进程最关键的特征是它拥有独立的虚拟地址空间，这使得它有别于其他进程，很多时候一个程序程序被执行同时伴随着一个新进程的创建，在有虚拟存储的情况下，创建一个进程，然后加载可执行文件并且执行，需要做三件事：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;创建一个独立的虚拟地址空间&lt;/li&gt;&#10;&lt;li&gt;读取可执行文件头，并且建立虚拟空间与可执行文件的映射关系&lt;/li&gt;&#10;&lt;li&gt;将CPU的指令寄存器设置成可执行文件的入口地址，启动运行&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="首先是创建虚拟地址空间"&gt;首先是创建虚拟地址空间&lt;/h4&gt;&#10;&lt;p&gt;我们知道虚拟地址空间是有一组页映射函数将虚拟空间的各个页映射到相应的物理空间，那么创建虚拟地址空间并不是创建空间而是创建这个映射结构，在i386下创建虚拟地址空间只要分配一个页目录就可以了，甚至不设置页映射关系，这些映射关系等到后面程序发生页错误的时候设置。页表上有各种标志位，其中可以知道这一块地址是否分配物理地址。&lt;/p&gt;&#10;&lt;h4 id="虚拟空间与文件映射"&gt;虚拟空间与文件映射&lt;/h4&gt;&#10;&lt;p&gt;上面的那一步映射是虚拟空间到物理内存的映射，当前是虚拟空间到可执行文件的映射。当程序发生页错误的时候，操作系统从物理内存分配一个物理页，然后将缺页从磁盘读到内存，再设置缺页的虚拟页和物理页的映射关系，这样程序继续运行。但是明显的一点是，缺页的时候需要知道缺的页是文件的哪个位置。&lt;/p&gt;&#10;&lt;h4 id="执行可执行文件"&gt;执行可执行文件&lt;/h4&gt;&#10;&lt;p&gt;操作系统设置CPU的指令寄存器将控制权交给进程，切换内核对战和CPU运行权限后，跳转到可执行文件的入口地址，即ELF文件头中的入口地址。&lt;/p&gt;&#10;&lt;h3 id="ELF文件映射"&gt;ELF文件映射&lt;/h3&gt;&#10;&lt;p&gt;linux中将进程虚拟空间的一个段叫做虚拟内存区域(VMA,virtual memory area)，比如操作系统创建进程后，会在进程的相应数据结构设置一个.text段的VMA,它在虚拟地址空间的地址为0x08048000~0x08049000，它对应的ELF文件的偏移为0的.text，它的属性是只读。&lt;br&gt;&#10;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/vma_to_elf.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;在操作系统里面，VMA除了被用来映射可执行文件里面的段，他还有其他的作用，操作系统使用VMA对进程的地址空间进行管理，我们知道进程在执行的时候需要用到栈(stack)、堆(heap)，事实上他们在内存空间也是以VMA的形式存在，比如我们查看nginx的各个VMA:&lt;br&gt;&#10;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/vma_maps_in_proc" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/inux-virtual-space-02.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="进程栈初始化"&gt;进程栈初始化&lt;/h3&gt;&#10;&lt;p&gt;进程刚启动的时候，需要知道一些进程的运行环境，最基本的就是系统环境变量和进程的运行参数。很常见的做法是操作系统在进程启动的时候将这些信息提前保存到进程的虚拟地址空间的栈上(stack VMA)。&lt;br&gt;&#10;&lt;img src="https://202002-1251969284.cos.ap-shanghai.myqcloud.com/args_top_stack_main.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="linux内核装载ELF过程"&gt;linux内核装载ELF过程&lt;/h3&gt;&#10;&lt;p&gt;我们在shell下执行一个可执行文件，首先在用户层面，bash进程会调用fork系统调用创建一个新的进程，然后新的进程调用execve()系统调用执行指定的ELF文件，进入exec()系统调用之后，Linux内核就开始真正的装载工作。在内核中，do_exec首先查找被执行文件，如果找到文件，则读取文件的前128字节。为什么这么做，Linux支持的可执行文件不止ELF，前128字节是判断文件格式的，开头的四个字节，称为魔数，通过对魔数的判断可以确定文件的类型和格式。比如ELF的可执行文件前四个字节为0x7F,E,L,F,java的可执行文件头为c,a,f,e.&lt;/p&gt;&#10;&lt;p&gt;这里我们只关心ELF的装载，具体步骤为：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;检查ELF的文件格式有效性，比如魔数，段检查&lt;/li&gt;&#10;&lt;li&gt;寻找动态链接的.interp段，设置动态链接库路径&lt;/li&gt;&#10;&lt;li&gt;根据ELF可执行文件的程序头，对ELF文件进行映射&lt;/li&gt;&#10;&lt;li&gt;初始化ELF进程环境，寄存器地址等&lt;/li&gt;&#10;&lt;li&gt;将系统调用的返回修改成ELF可执行文件的入口点，这个入口点取决于链接的方式，对于静态链接，入口是e_entry地址，对于动态链接，程序入口点是动态连接器。&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>程序员的自我修养-线程安全</title><link>https://www.oneyearago.me/2020/02/04/Thread-safe/</link><pubDate>Tue, 04 Feb 2020 20:24:39 +0800</pubDate><guid>https://www.oneyearago.me/2020/02/04/Thread-safe/</guid><description>&lt;h3 id="原子操作"&gt;原子操作&lt;/h3&gt;&#10;&lt;p&gt;自增(++i)操作在多线程环境下出现错误是因为这个操作被编译成的代码不止一条指令，因此在执行的时候可能执行到一半被系统调度打乱，去执行别的代码。我们把单指令的操作称为原子的(atomic),因为无论如何，单条指令的执行时不会被打乱的，为了避免出错，很多体系结构都提供了一些常用操作的原子指令。&lt;/p&gt;&#10;&lt;h3 id="同步与锁"&gt;同步与锁&lt;/h3&gt;&#10;&lt;p&gt;为了避免多个线程同时读写同一个数据而产生不可预料的后果，我们要将多个线程对同一个数据的访问同步。所谓同步，指一个线程正在访问数据的时候，其他线程不能对同一个数据访问。&lt;/p&gt;&#10;&lt;p&gt;同步的最常用方法是使用锁。&lt;strong&gt;==锁是一种非强制的机制==&lt;/strong&gt;，每个线程在访问之前试图去获取锁，并在访问之后释放锁，如果锁在被访问的时候已经被占用，线程等待，直到锁重新可用。&lt;/p&gt;&#10;&lt;p&gt;二元信号量是最简单的一种锁，她只有两种状态，占用和非占用，它适合只能被唯一线程访问的资源；对于允许多个线程并发访问的资源，多元信号量简称信号量(semaphore)，她是一个很好的选择，多元信号量维护一个计数。&lt;/p&gt;&#10;&lt;h3 id="互斥量-mutex"&gt;互斥量 (mutex)&lt;/h3&gt;&#10;&lt;p&gt;和二元信号量类似，资源仅同时允许一个线程访问，但和信号量不同的是，信号量在整个系统中可以被任意线程捕获，也就是说同一个信号量可以被系统的一个线程获取后由另一个线程释放，而互斥量则要求哪个线程获取了互斥量，哪个线程就要释放整个锁，其他线程越俎代庖释放信号量是无效的。&lt;/p&gt;&#10;&lt;h3 id="临界区-Critical-section"&gt;临界区 (Critical section)&lt;/h3&gt;&#10;&lt;p&gt;临界区是比互斥量更严格的同步手段，在术语中，把临界区的锁的获取称为进入临界区，把锁的释放称为离开临界区。临界区和互斥量的区别在于，互斥量和信号量是在系统的任何进程都是可见的，然而临界区的作用范围是仅限于本进程，其他的进程无法获取。除此之外，临界区和互斥量有相同的性质。&lt;/p&gt;&#10;&lt;h3 id="读写锁"&gt;读写锁&lt;/h3&gt;&#10;&lt;p&gt;致力于一种更加特定场合的同步，对于一段数据，多个线程同时读总是没问题的，但假设操作不是原子的，只要有任何一个线程试图对这个数据进行修改，就必须使用同步手段避免出错。假如我们使用上述信号量，互斥量、临界区的方式进行修改，尽管正确，但是比较麻烦，对于读频繁但是写入比较少的情况，也比较低效。&lt;/p&gt;&#10;&lt;p&gt;读写锁可以解决这个问题，对于同一个锁，读写锁有两种获取方式，共享的或者是独占的，当锁处于自由的状态时，试图以任意一种方式获取锁都能成功；如果锁处于共享的状态，其他线程以共享的方式获取锁仍然可以成功，此时这个锁分配给了多个线程，然而，如果其他线程试图以独占的方式获取锁已经处于共享状态的锁，它必须等待锁被所有的线程释放；相应的，处于独占状态的锁将阻止任何其他线程获取该锁。&lt;/p&gt;&#10;&lt;h3 id="过度优化"&gt;过度优化&lt;/h3&gt;&#10;&lt;p&gt;线程安全是一个非常烫手的山芋，因为即使合理地使用锁，也不一定能保证线程安全，这是落后的编译器技术已经无法满足日益增长的并发需求。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;x=0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Thread1 Thread2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lock(); lock();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;x++; x++;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;unlock(); unlock();&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果编译器为了提高x的访问速度，把x放到某个寄存器里面，由于CPU的动态调度和编译器为了优化而交换毫不相干的相邻指令，此时锁失效。&lt;/p&gt;&#10;&lt;p&gt;我们可以使用 volatile关键字阻止过度优化，其基本可以做2件事，一是阻止编译器为了提高速度将一个变量缓存到寄存器里面而不写会，而是阻止编译器调整操作volatile变量的指令顺序。&lt;/p&gt;&#10;&lt;h3 id="cpu-乱序执行"&gt;cpu 乱序执行&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#define barrier() __asm__ volatile (&amp;#34;lwsync&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;volatile T* pInst = 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;T* GetInstance()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if(!pInst)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Lock();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; T* temp = new T;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; barrier();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pInst = temp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; unLock();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return pInst;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;上面的代码加了一个barrier，是为了解决CPU的乱序执行。C++里面的new实际包含两个步骤，1、分配内存；2.调用构造函数。pInst = new T;包含三个步骤，1.分配内存，2，在内存的位置调用构造函数，3.将内存的值付给pInst, 其中2 3 是可能乱序执行的。&lt;/p&gt;</description></item><item><title>一种定时器的实现</title><link>https://www.oneyearago.me/2019/12/30/linklist_timer/</link><pubDate>Mon, 30 Dec 2019 00:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2019/12/30/linklist_timer/</guid><description>&lt;h3 id="概述"&gt;概述&lt;/h3&gt;&#10;&lt;p&gt;一个Timer的实现需要具备以下几个行为:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;StartTimer(Interval, ExpiryAction)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;注册一个时间间隔为 Interval 后执行 ExpiryAction 的定时器实例，其中，返回 TimerId 以区分在定时器系统中的其他定时器实例。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;StopTimer(TimerId)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;根据 TimerId 找到注册的定时器实例并执行 Stop 。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;PerTickBookkeeping()&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;在一个 Tick 时间粒度内，定时器系统需要执行的动作，它最主要的行为，就是检查定时器系统中，是否有定时器实例已经到期。&lt;/p&gt;&#10;&lt;p&gt;具体的代码实现思路就是：在StartTimer的时候，把 当前时间 + Interval 作为key放入一个容器，然后在Loop的每次Tick里，从容器里面选出一个最小的key与当前时间比较，如果key小于当前时间，则这个key代表的timer就是expired，需要执行它的ExpiryAction(一般为回调)。&lt;/p&gt;&#10;&lt;h3 id="链表的实现"&gt;链表的实现&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;精度是 1ms&lt;/li&gt;&#10;&lt;li&gt;最长时间是10min,延长时间可以增加 slot数量,slot时间的间隔是 1ms&lt;/li&gt;&#10;&lt;li&gt;通过继承Timer父类，在子类重写timeout实现 超时回调&lt;/li&gt;&#10;&lt;li&gt;每次都需要遍历超过时间的所有链表，时间复杂度为O(n)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="执行方式"&gt;执行方式&lt;/h3&gt;&#10;&lt;p&gt;每次从上次执行的时间，遍历每个链表上挂的timer是不是到期，如果到期了，就执行对应的超时函数，并移除定时器，把这个环拉直看就可以了:&lt;br&gt;&#10;&lt;img src="https://201910-1251969284.cos.ap-shanghai.myqcloud.com/time_round_1.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int TimerMgr::TimeRun()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;Tick();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;int timeout_count = 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;int pass = currTime - lastRunTime;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;uint64_t time = lastRunTime + 1;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;for (int i = 0; i &amp;lt; pass; i++)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;int index = time % timeLen;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;while(slot[index]-&amp;gt;Size() &amp;gt; 0)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;TimerNode* node = slot[index]-&amp;gt;Serve();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;TimerNodeMap::iterator it;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;it = nodemap.find(node-&amp;gt;t-&amp;gt;getId());&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;if (it != nodemap.end())&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;if (node == NULL)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&#9;ERROR(&amp;#34;node == NULL&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&#9;continue;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;if (node-&amp;gt;t == NULL)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&#9;ERROR(&amp;#34;timer == NULL&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&#9;continue;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;DEBUG(&amp;#34;time out %d&amp;#34;, index);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;node-&amp;gt;t-&amp;gt;Timeout();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;nodemap.erase(node-&amp;gt;t-&amp;gt;getId());&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;timeout_count++;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;TimerPool.Free(reinterpret_cast&amp;lt;char*&amp;gt;(node-&amp;gt;t));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;NodePool.Free(reinterpret_cast&amp;lt;char*&amp;gt;(node));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;time++;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;lastRunTime = currTime;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;return timeout_count;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="example"&gt;example&lt;/h3&gt;&#10;&lt;p&gt;子类:&lt;/p&gt;</description></item><item><title>tcp socket api</title><link>https://www.oneyearago.me/2019/12/11/socket_api/</link><pubDate>Wed, 11 Dec 2019 00:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2019/12/11/socket_api/</guid><description>&lt;h3 id="socket是网络协议栈的抽象"&gt;socket是网络协议栈的抽象&lt;/h3&gt;&#10;&lt;h3 id="socket-api在tcp中的体现"&gt;socket api在tcp中的体现&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://201911-1251969284.cos.ap-shanghai.myqcloud.com/tcp-sync-queue-and-accept-queue.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;TCP三次握手后有个accept队列，进到这个队列才能从Listen变成accept，默认backlog 值是50。&lt;/p&gt;&#10;&lt;p&gt;如上图所示，这里有两个队列：syns queue(半连接队列）；accept queue（全连接队列）&lt;br&gt;&#10;三次握手中，在第一步server收到client的syn后，把相关信息放到半连接队列中，同时回复syn+ack给client，第三步的时候server收到client的ack，如果这时全连接队列没满，那么从半连接队列拿出相关信息放入到全连接队列中。&lt;/p&gt;&#10;&lt;p&gt;当接收到3次握手中的ACK分组时，将它们移动到accept队列。 显而易见，accept系统调用只是简单地从完成队列中取出连接。 在这种情况下，listen syscall的backlog参数表示完成队列的大小。&lt;/p&gt;&#10;&lt;h3 id="code"&gt;code&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpechosvr.c&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;*/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;stdio.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;stdlib.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;sys/types.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;sys/socket.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;netinet/in.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;errno.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;string.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#define SRVIP &amp;#34;127.0.0.1&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#define SRVPORT 10005&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#define MAX_NUM 1024&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int main()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int serverSock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if(serverSock &amp;lt; 0)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;socket creation failed\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(-1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;socket create successfully.\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct sockaddr_in serverAddr;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; memset(&amp;amp;serverAddr,0,sizeof(serverAddr));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; serverAddr.sin_family=AF_INET;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; serverAddr.sin_port = htons((u_short)SRVPORT);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; serverAddr.sin_addr.s_addr = inet_addr(SRVIP);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if(bind(serverSock, (struct sockaddr*)&amp;amp;serverAddr, sizeof(struct sockaddr))==-1)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;Bind error.IP[%s], Port[%d]\n&amp;#34;, SRVIP, serverAddr.sin_port);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(-1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;Bind successful.IP[%s], Port[%d]\n&amp;#34;, SRVIP, serverAddr.sin_port);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /* 把sock链接进入监听哈希表listening_hash &amp;amp; 创建半连接队列的实例，初始化全连接队列 */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if(listen(serverSock,10)==-1)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;Listen error!\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;Listening on port[%d]\n&amp;#34;, serverAddr.sin_port);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; char recvBuf[MAX_NUM]={0};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; while(1)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct sockaddr clientAddr;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int size = sizeof(clientAddr);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /* 从全连接队列中取 */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int clientSock = accept(serverSock, &amp;amp;clientAddr, &amp;amp;size);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;***SYS*** New client touched.\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; while(1)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; //一直接收客户端socket的send操作&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if(recv(clientSock, recvBuf, MAX_NUM, 0) == -1)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;read error.\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; else&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;receiv from client:%s\n&amp;#34;,recvBuf);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; // 向客户端发送套接字&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if(send(clientSock, recvBuf , sizeof(recvBuf), MSG_NOSIGNAL) == -1)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;Send error!\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; else&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf(&amp;#34;Send to client:%s\n&amp;#34;, recvBuf);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if( strcmp(recvBuf, &amp;#34;bye&amp;#34;) == 0)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; break;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; memset(recvBuf, 0, sizeof(recvBuf));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; close(clientSock);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; close(serverSock);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="REF"&gt;REF&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="http://jm.taobao.org/2017/05/25/525-1/"&gt;关于TCP 半连接队列和全连接队列&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="http://veithen.io/2014/01/01/how-tcp-backlog-works-in-linux.html"&gt;How TCP backlog works in Linux&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>Understanding TCPIP Network Stack</title><link>https://www.oneyearago.me/2019/11/28/Understanding_TCPIP_Network_Stack/</link><pubDate>Thu, 28 Nov 2019 23:58:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/11/28/Understanding_TCPIP_Network_Stack/</guid><description>&lt;h3 id="概述"&gt;概述&lt;/h3&gt;&#10;&lt;p&gt;TCP &amp;amp; socket 总结，&lt;/p&gt;&#10;&lt;h3 id="网卡-network-interface-card"&gt;网卡 network interface card&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://201911-1251969284.cos.ap-shanghai.myqcloud.com/network_interface.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;网卡需要有驱动才能工作，驱动是加载到内核中的模块，负责衔接网卡和内核的网络模块，驱动在加载的时候将自己注册进网络模块，当相应的网卡收到数据包时，网络模块会调用相应的驱动程序处理数据。&lt;/p&gt;&#10;&lt;p&gt;内核如何从网卡接受数据，传统的经典过程：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;1、数据到达网卡；&lt;/li&gt;&#10;&lt;li&gt;2、网卡产生一个中断给内核；&lt;/li&gt;&#10;&lt;li&gt;3、内核使用I/O指令，从网卡I/O区域中去读取数据；&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;但是，这一种方法，有一种重要的问题，就是大流量的数据来到，网卡会产生大量的中断，内核在中断上下文中，会浪费大量的资源来处理中断本身。所以，一个问 题是，“可不可以不使用中断”，这就是轮询技术，所谓NAPI技术，说来也不神秘，就是说，内核屏蔽中断，然后隔一会儿就去问网卡，“你有没有数据 啊？”&lt;/p&gt;&#10;&lt;p&gt;另一个问题，就是从网卡的I/O区域，包括I/O寄存器或I/O内存中去读取数据，这都要CPU去读，也要占用CPU资源，“CPU从I/O区域 读，然后把它放到内存（这个内存指的是系统本身的物理内存，跟外设的内存不相干，也叫主内存）中”。于是自然地，就想到了DMA技术——让网卡直接从主内 存之间读写它们的I/O数据。&lt;/p&gt;&#10;&lt;h4 id="优化后"&gt;优化后&lt;/h4&gt;&#10;&lt;p&gt;&lt;img src="https://201911-1251969284.cos.ap-shanghai.myqcloud.com/ring-buffer.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;NIC (network interface card) 在系统启动过程中会向系统注册自己的各种信息，系统会分配 Ring Buffer 队列也会分配一块专门的内核内存区域给 NIC 用于存放传输上来的数据包。struct sk_buff 是专门存放各种网络传输数据包的内存接口，在收到数据存放到 NIC 专用内核内存区域后，sk_buff 内有个 data 指针会指向这块内存。&lt;/p&gt;&#10;&lt;p&gt;Ring Buffer 队列内存放的是一个个 Packet Descriptor ，其有两种状态： ready 和 used 。初始时 Descriptor 是空的，指向一个空的 sk_buff，处在 ready 状态。当有数据时，DMA 负责从 NIC 取数据，并在 Ring Buffer 上按顺序找到下一个 ready 的 Descriptor，将数据存入该 Descriptor 指向的 sk_buff 中，并标记槽为 used。因为是按顺序找 ready 的槽，所以 Ring Buffer 是个 FIFO 的队列。&lt;/p&gt;</description></item><item><title>TCP的几个问题</title><link>https://www.oneyearago.me/2019/11/25/net_shake_hand/</link><pubDate>Mon, 25 Nov 2019 01:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2019/11/25/net_shake_hand/</guid><description>&lt;h3 id="IP-header"&gt;IP header&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://201911-1251969284.cos.ap-shanghai.myqcloud.com/ip_header.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="UDP-header"&gt;UDP header&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://201911-1251969284.cos.ap-shanghai.myqcloud.com/UDP-Header-Format.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="TCP-header"&gt;TCP header&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://201911-1251969284.cos.ap-shanghai.myqcloud.com/TCP-Header-Format.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h3 id="MTU-amp-MSS"&gt;MTU &amp;amp; MSS&lt;/h3&gt;&#10;&lt;p&gt;MSS=MTU-IP包头长-TCP包头长&lt;/p&gt;&#10;&lt;h3 id="三次握手"&gt;三次握手&lt;/h3&gt;&#10;&lt;p&gt;交换初始序列号 交换TCP窗口大小信息&lt;/p&gt;&#10;&lt;h3 id="四次挥手"&gt;四次挥手&lt;/h3&gt;&#10;&lt;p&gt;可以三次挥手吗，可以，ack+fin一起发就是三次，启用延迟确认就有三次挥手&lt;/p&gt;&#10;&lt;h3 id="2MSL等待状态"&gt;2MSL等待状态&lt;/h3&gt;&#10;&lt;p&gt;TIME_WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL（Maximum Segment Lifetime）。它是任何报文段被丢弃前在网络内的最长时间。我们知道这个时间是有限的，因为TCP报文段以IP数据报在网络内传输，而IP数据报则有限制其生存时间的TTL字段。&lt;/p&gt;&#10;&lt;p&gt;对一个具体实现所给定的MSL值，处理的原则是：当TCP执行一个主动关闭，并发回最后一个ACK，该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失（另一端超时并重发最后的FIN）。&lt;/p&gt;&#10;&lt;p&gt;这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间，定义这个连接的插口（客户的IP地址和端口号，服务器的IP地址和端口号）不能再被使用。这个连接只能在2MSL结束后才能再被使用。&lt;/p&gt;&#10;&lt;h3 id="Nagle算法"&gt;Nagle算法&lt;/h3&gt;&#10;&lt;p&gt;Nagle算法是时代的产物，因为当时网络带宽有限。而当前的局域网、广域网的带宽则宽裕得多，所以目前的TCP/IP协议栈默认将Nagle算法关闭，即通过SO_NODELAY = 1&lt;/p&gt;&#10;&lt;h3 id="滑动窗口"&gt;滑动窗口&lt;/h3&gt;&#10;&lt;p&gt;性能: 停止等待协议 -&amp;gt; 滑动窗口&lt;br&gt;&#10;协议: GBN and SR&lt;/p&gt;&#10;&lt;h3 id="流量控制"&gt;流量控制&lt;/h3&gt;&#10;&lt;p&gt;发送方不能发的太快，接收不过来&lt;/p&gt;&#10;&lt;h3 id="拥塞控制"&gt;拥塞控制&lt;/h3&gt;&#10;&lt;p&gt;成因: 中间路由器缓存有限，发送带宽有限，更多没必要的重传，多跳&lt;br&gt;&#10;影响: 丢包 &amp;amp; 时延&lt;br&gt;&#10;方案: 观察 时延 丢包判断是否拥塞(TCP采用)&lt;br&gt;&#10;策略: 慢启动，先指数增，遇到丢包 /2 再线性增&lt;/p&gt;&#10;&lt;h3 id="状态图"&gt;状态图&lt;/h3&gt;&#10;&lt;p&gt;&lt;img src="https://201911-1251969284.cos.ap-shanghai.myqcloud.com/tcp_all_stat" alt="image"&gt;&lt;/p&gt;</description></item><item><title>awk note</title><link>https://www.oneyearago.me/2019/10/29/awk/</link><pubDate>Tue, 29 Oct 2019 17:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2019/10/29/awk/</guid><description>&lt;h3 id="处理流程"&gt;处理流程&lt;/h3&gt;&#10;&lt;p&gt;当使用 awk 命令处理一个或者多个文件时，它会依次读取文件的每一行内容，然后对其进行处理，awk 命令默认从 stdio 标准输入获取文件内容，awk 使用一对单引号来表示一些可执行的脚本代码，在可执行脚本代码里面，使用一对花括号来表示一段可执行代码块，可以同时存在多个代码块。awk 的每个花括号内同时可以有多个指令，每一个指令用分号分隔，awk 其实就是一个脚本编程语言。&lt;/p&gt;&#10;&lt;p&gt;格式: awk ‘condition action’ filename&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;awk -F &amp;#39;[|:;]&amp;#39; &amp;#39;{if ($1 &amp;gt; &amp;#34;m&amp;#34;) print $1; else print &amp;#34;---&amp;#34;}&amp;#39; /etc/passwd&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;脚本化:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#! /bin/awk -f&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;BEGIN {print &amp;#34;begin to awk\n&amp;#34;} &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$s ~ /beijing/ &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;END { print &amp;#34;end of awk&amp;#34; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# xxx.awk xxx.data&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="内置变量"&gt;内置变量&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;变量&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;含义&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;行内容&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;第一列字段&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;NF&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;当前行有多少个字段&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$NF&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;最后一个字段&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;NR&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;当前处理的是第几行&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FILENAME&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;当前文件名&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;字段分隔符，默认是空格和制表符。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;RS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;行分隔符，用于分割每一行，默认是换行符。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OFS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;输出字段的分隔符，用于打印时分隔字段，默认为空格。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ORS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;输出记录的分隔符，用于打印时分隔记录，默认为换行符。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OFMT&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;数字输出的格式，默认为％.6g。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="内置函数"&gt;内置函数&lt;/h3&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;func&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;含义&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;tolower()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;字符转为小写&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;length()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;返回字符串长度&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;substr()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;返回子字符串&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;sin()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;正弦&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;cos()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;余弦&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;sqrt()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;平方根&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;rand()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;随机数&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="组合使用"&gt;组合使用&lt;/h3&gt;&#10;&lt;p&gt;使用 awk 过滤 history 输出，找到最常用的命令&lt;/p&gt;</description></item><item><title>linux定时器时间轮算法</title><link>https://www.oneyearago.me/2019/10/24/linux_timer/</link><pubDate>Thu, 24 Oct 2019 23:58:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/10/24/linux_timer/</guid><description>&lt;h3 id="时间轮实现"&gt;时间轮实现&lt;/h3&gt;&#10;&lt;p&gt;Linux定时器分为低精度定时器和高精度定时器两种类型，内核对其均有实现。本文讨论的是我们在应用程序开发中比较常见的低精度定时器。作为常用的基础组件，定时器常用的几种实现方法包括：基于排序链表实现、基于小根堆实现、基于红黑树实现、基于时间轮实现。本文讲解的是时间复杂度最优，也是linux内核采用的基于时间轮的实现方式。&lt;/p&gt;&#10;&lt;p&gt;采用有序链表实现的定时器，添加定时器的时间复杂度为O(n)；采用小根堆或红黑树实现的定时器，添加定时器的时间复杂度为O(lgn)。之所以没法做到O(1)的复杂度，究其原因是所有定时器节点挂在一条链表（或一棵树）上。时间轮算法的核心思路是将定时器散列到多条链上，是典型的空间换时间的策略。下文从单个时间轮出发讲解，逐步扩展至linux实现定时器所采用的多级时间轮算法。&lt;/p&gt;&#10;&lt;h4 id="简单的单时间轮"&gt;简单的单时间轮&lt;/h4&gt;&#10;&lt;p&gt;单时间轮只有一个由bucket串起来的轮子，下图所示的时间轮有8个bucket，每个bucket下链接着未来对应时刻到期的节点。假设图中相邻bucket到期时间的间隔为slot=1s，从当前时刻0s开始计时，1s时到期的定时器节点挂在bucket[1]下，2s时到期的定时器节点挂在bucket[2]下……当tick检查到时间过去了1s时，bucket[1]下所有节点执行超时动作，当时间到了2s时，bucket[2]下所有节点执行超时动作…….&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://201910-1251969284.cos.ap-shanghai.myqcloud.com/time_round_1.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;由于bucket是一个数组，能直接根据下标定位到具体定时器节点链，因此添加删除节点、定时器到期执行的时间复杂度均为O(1)。&lt;/p&gt;&#10;&lt;p&gt;但使用这个定时器所受的限制也显而易见：待添加的timer到期时间必须在8s以内。这显然不能满足实际需求。当然要扩展也很容易，直接增加bucket的个数就可以了。在 Linux 系统中，我们可以设置slot为1个jiffy(1/HZ)的定时器，假设最大的到期时间范围要达到 2^32个 jiffies，如果采用上面这样的单时间轮，我们就需要2^32个 bucket，这会带来巨大的内存消耗，显然是需要优化改进的。&lt;/p&gt;&#10;&lt;h4 id="改进的单时间轮"&gt;改进的单时间轮&lt;/h4&gt;&#10;&lt;p&gt;改进的单时间轮其实是一个对时间和空间折中的思路，即不会像单时间轮那样有O(1)的时间复杂度，但也不会像单时间轮那样对bucket个数有巨大的需求。其原理也很简单，就是每个bucket不单可以挂接到期时间expire=slot的定时器，还可挂接expire%N=slot的定时器(N为bucket个数)。这也正好顺应时间轮的轮回作用。如图2所示，定时器中expire表示到期时间，rotation表示节点在时间轮转了几圈后才到期。当当前时间指针指向某个bucket时，不能像简单时间轮那样直接对bucket下的所有节点执行超时动作，而是需要对链表中节点遍历一遍，判断轮子转动的次数是否等于节点中的rotation值，当两者相等时，方可执行超时操作。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://201910-1251969284.cos.ap-shanghai.myqcloud.com/time_round_2.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="多时间轮"&gt;多时间轮&lt;/h4&gt;&#10;&lt;p&gt;上面所述的时间轮都是单枪匹马战斗的，因此很难在时间和空间上都达到理想效果。Linux所实现的多时间轮算法，借鉴了日常生活中水表的度量方法，通过低刻度走得快的轮子带动高一级刻度轮子走动的方法，达到了仅使用较少刻度即可表示很大范围度量值的效果。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://201910-1251969284.cos.ap-shanghai.myqcloud.com/time_round_3.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;Linux定时器时间轮分为5个级别的轮子(tv1 ~ tv5)，如图3所示。每个级别的轮子的刻度值(slot)不同，规律是次级轮子的slot等于上级轮子的slot之和。Linux定时器slot单位为1jiffy，tv1轮子分256个刻度，每个刻度大小为1jiffy。tv2轮子分64个刻度，每个刻度大小为256个jiffy，即tv1整个轮子所能表达的范围。相邻轮子也只有满足这个规律，才能达到“低刻度轮子转一圈，高刻度轮子走一格”的效果。tv3，tv4，tv5也都是分为64个刻度，因此容易算出，最高一级轮子tv5所能表达的slot范围达到了256&lt;em&gt;64&lt;/em&gt;64&lt;em&gt;64&lt;/em&gt;64 = 2^32 jiffies。&lt;/p&gt;&#10;&lt;p&gt;Linux时间轮定时器算法的关键在于添加定时器操作和时间轮进位迁移链表操作。先来说添加定时器。添加定时器的关键又在于知道每个时间轮每一个刻度所能表示的到期时间的范围。图4列出了每一级时间轮能度量的jiffies的大小。假设有一个定时器在1000个jiffies后到期，根据图4容易看出其应该挂在tv2轮上。tv2轮每个刻度表示的大小为256个jiffies，则其应该挂在(1000/256)=3即第三个bucket上。&lt;/p&gt;&#10;&lt;p&gt;Linux在定时器到期检查上的操作也实现得很巧妙。假设curr_time=0x12345678，那么下一个检查的时刻为0x12345679。如果tv1.bucket[0x79]上链表非空，则下一个检查时刻tv1.bucket[0x79]上的定时器节点超时。如果curr_time到了0x12345700，低8位为空，说明有进位产生，这时移出8～13位对应的定时器链表(即正好对应着tv2轮)，重新加入定时器系统，这就完成了一次进位迁移操作。同样地，当curr_time的第8-13位为0时，这表明tv2轮对tv3轮有进位发生，将curr_time第14-19位的值作为下标，移出tv3中对应的定时器链表，然后将它们重新加入到定时器系统中来。tv4,tv5依次类推。之所以能够根据curr_time来检查超时链，是因为tv1~tv5轮的度量范围正好依次覆盖了整型的32位：tv1(1-8位)，tv2(9-14位)，tv3(15-20位)，tv4(21-26位)，tv5(27-32位)；而curr_time计数的递增中，低位向高位的进位正是低级时间轮转圈带动高级时间轮走动的过程。&lt;/p&gt;&#10;&lt;h3 id="对比"&gt;对比&lt;/h3&gt;&#10;&lt;p&gt;最后比较一下多级时间轮和单个简单时间轮的时间复杂度及空间复杂度：linux使用了总计256+64+64+64+64=512个bucket，即可实现[0，2^32) jiffies的超时范围。相比简单的单时间轮，时间上仅仅多了1/256次(为约等于值，忽略了tv2以上产生的进位操作)的链表迁移操作耗时。可以认为其添加、删除定时器节点及到期check的操作时间复杂度均为O(1)。&lt;/p&gt;&#10;&lt;h4 id="引用"&gt;引用&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;KM&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>进程通信 &amp; PIPE</title><link>https://www.oneyearago.me/2019/10/24/IPC_and_pipe/</link><pubDate>Thu, 24 Oct 2019 23:58:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/10/24/IPC_and_pipe/</guid><description>&lt;h3 id="历史"&gt;历史&lt;/h3&gt;&#10;&lt;p&gt;UNIX两大贡献者贝尔实验室和BSD，在进程之间通信侧重不同，前者基于内核对进程之间的通信手段进行了改进，形成了“System V IPC”，而后者则是基于网络形成了套接字。&lt;/p&gt;&#10;&lt;p&gt;而POSIX则是IEEE制定的标准，目的是为运行在不同操作系统上的软件提供统一的接口，实现者则是不同的操作系统内核开发人员。&lt;/p&gt;&#10;&lt;p&gt;System V 以及POSIX 对信号量、共享内存、消息队列等进程之间共享方式提供了自己的解决方案。&lt;/p&gt;&#10;&lt;p&gt;在观察使用进程间通信手段后，会发现在多线程中使用的基本是POSIX标准提供的接口函数，而多进程则是基于System V。&lt;/p&gt;&#10;&lt;h3 id="管道"&gt;管道&lt;/h3&gt;&#10;&lt;p&gt;pipe和fifo用的不多了，让我们从Nginx那里学一个 全双工的管道: socketpair&lt;br&gt;&#10;&lt;img src="https://201910-1251969284.cos.ap-shanghai.myqcloud.com/socket_pair_pipe.bmp" alt="image"&gt;&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;iostream&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;unistd.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;sys/uio.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;sys/socket.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;stdlib.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;#include &amp;lt;stdio.h&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssize_t&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sock_fd_write(int sock, void *buf, ssize_t buflen, int fd)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ssize_t size;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct msghdr msg;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct iovec iov;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; union {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct cmsghdr cmsghdr;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; char control[CMSG_SPACE(sizeof (int))];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; } cmsgu;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct cmsghdr *cmsg;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; iov.iov_base = buf;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; iov.iov_len = buflen;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_name = NULL;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_namelen = 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_iov = &amp;amp;iov;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_iovlen = 1;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (fd != -1) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_control = cmsgu.control;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_controllen = sizeof(cmsgu.control);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmsg = CMSG_FIRSTHDR(&amp;amp;msg);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmsg-&amp;gt;cmsg_len = CMSG_LEN(sizeof (int));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmsg-&amp;gt;cmsg_level = SOL_SOCKET;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmsg-&amp;gt;cmsg_type = SCM_RIGHTS;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf (&amp;#34;passing fd %d\n&amp;#34;, fd);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; *((int *) CMSG_DATA(cmsg)) = fd;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; } else {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_control = NULL;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_controllen = 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf (&amp;#34;not passing fd\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; size = sendmsg(sock, &amp;amp;msg, 0);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (size &amp;lt; 0)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; perror (&amp;#34;sendmsg&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return size;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssize_t&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sock_fd_read(int sock, void *buf, ssize_t bufsize, int *fd)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ssize_t size;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (fd) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct msghdr msg;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct iovec iov;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; union {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct cmsghdr cmsghdr;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; char control[CMSG_SPACE(sizeof (int))];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; } cmsgu;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; struct cmsghdr *cmsg;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; iov.iov_base = buf;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; iov.iov_len = bufsize;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_name = NULL;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_namelen = 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_iov = &amp;amp;iov;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_iovlen = 1;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_control = cmsgu.control;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; msg.msg_controllen = sizeof(cmsgu.control);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; size = recvmsg (sock, &amp;amp;msg, 0);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (size &amp;lt; 0) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; perror (&amp;#34;recvmsg&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmsg = CMSG_FIRSTHDR(&amp;amp;msg);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (cmsg &amp;amp;&amp;amp; cmsg-&amp;gt;cmsg_len == CMSG_LEN(sizeof(int))) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (cmsg-&amp;gt;cmsg_level != SOL_SOCKET) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fprintf (stderr, &amp;#34;invalid cmsg_level %d\n&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmsg-&amp;gt;cmsg_level);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (cmsg-&amp;gt;cmsg_type != SCM_RIGHTS) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fprintf (stderr, &amp;#34;invalid cmsg_type %d\n&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmsg-&amp;gt;cmsg_type);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; *fd = *((int *) CMSG_DATA(cmsg));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf (&amp;#34;received fd %d\n&amp;#34;, *fd);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; } else&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; *fd = -1;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; } else {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; size = read (sock, buf, bufsize);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (size &amp;lt; 0) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; perror(&amp;#34;read&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return size;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;void&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;child(int sock)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int fd;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; char buf[16];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ssize_t size;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep(1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; for (;;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; size = sock_fd_read(sock, buf, sizeof(buf), &amp;amp;fd);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (size &amp;lt;= 0)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; break;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf (&amp;#34;read %d\n&amp;#34;, (int) size);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (fd != -1) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; write(fd, &amp;#34;hello, world\n&amp;#34;, 13);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; close(fd);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;void&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;parent(int sock)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ssize_t size;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int i;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int fd;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fd = 1;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; size = sock_fd_write(sock, (void *) &amp;#34;1&amp;#34;, 1, 1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; printf (&amp;#34;wrote %d\n&amp;#34;, (int) size);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;main(int argc, char **argv)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int sv[2];&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int pid;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) &amp;lt; 0) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; perror(&amp;#34;socketpair&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; switch ((pid = fork())) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; case 0:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; close(sv[0]);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; child(sv[1]);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; break;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; case -1:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; perror(&amp;#34;fork&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; exit(1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; default:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; close(sv[1]);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; parent(sv[0]);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; break;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>临界区、信号量、互斥锁、自旋锁与原子操作</title><link>https://www.oneyearago.me/2019/08/18/Mutex/</link><pubDate>Sun, 18 Aug 2019 23:58:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/08/18/Mutex/</guid><description>&lt;h3 id="临界区、信号量、互斥锁、自旋锁与原子操作"&gt;临界区、信号量、互斥锁、自旋锁与原子操作&lt;/h3&gt;&#10;&lt;h4 id="临界区"&gt;临界区&lt;/h4&gt;&#10;&lt;p&gt;程序想要使用共享资源，必然通过一些指令去访问这些资源，若多个任务都访问同一资源，那么访问该资源的指令代码组成的区域称临界区。简而言之，临界区是代码&lt;/p&gt;&#10;&lt;h4 id="信号量"&gt;信号量&lt;/h4&gt;&#10;&lt;p&gt;信号量简单的说是一种计数器，用P/V操作表示减和增。&lt;br&gt;&#10;增加操作包括两个微操作：&lt;/p&gt;&#10;&lt;p&gt;增加：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;将信号量的值加一&lt;/li&gt;&#10;&lt;li&gt;唤醒此信号量上等待的线程&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;减少：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;判断信号量的值是否大于0&lt;/li&gt;&#10;&lt;li&gt;如果值大于0，则将信号量减1&lt;/li&gt;&#10;&lt;li&gt;若果信号量等于0，则当前线程将自己阻塞&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;信号量的值代表资源剩余量，我们可以用二元信号量实现锁。&lt;/p&gt;&#10;&lt;h4 id="自旋锁"&gt;自旋锁&lt;/h4&gt;&#10;&lt;p&gt;如果进线程无法取得锁，进线程不会立刻放弃CPU时间片，而是一直申请CPU时间片轮询自旋锁，直到获取为止，一般应用于加锁时间很短（1ms左右或更低）的场景。&lt;/p&gt;&#10;&lt;p&gt;这可以避免进程因被识别为“资源不足”而被操作系统置入休眠队列，从而避免不必要的上下文切换开销；但缺点是，它会导致“申请不到锁时执行死循环”，使得CPU核心占用100%——如果是单核单线程CPU，它就白白发一个时间片的热然后失去执行权（因为它占用了时间片，导致能释放资源给它的进/线程压根得不到执行机会）；只有在多CPU和/或多核和/或多线程硬件平台上、且这个锁一定会在远短于一个时间片的时间内被请求到，它才可能真正提高效率（否则又是白白浪费时间、电力让CPU发热了）。&lt;/p&gt;&#10;&lt;h4 id="互斥锁"&gt;互斥锁&lt;/h4&gt;&#10;&lt;p&gt;自旋锁”是一种“申请不到也不知会操作系统”的锁。其它锁都是“申请不到就通知操作系统：资源不足，我没法干活了，申请休息”。 于是操作系统暂停当前进程（线程）并将其置于等待/休眠队列，腾出它的CPU给其它进/线程使用；直到另外一个进程（线程）释放锁、它才可以再次得到执行机会。&lt;/p&gt;&#10;&lt;p&gt;有的资源同时只允许一个访问，无论读写；于是我们抽象它为“互斥锁”。&lt;/p&gt;&#10;&lt;h4 id="原子操作"&gt;原子操作&lt;/h4&gt;&#10;&lt;p&gt;原子操作，就是不能被更高等级中断抢夺优先的操作。由于操作系统大部分时间处于开中断状态，所以，一个程序在执行的时候可能被优先级更高的线程中断。而有些操作是不能被中断的，不然会出现无法还原的后果，这时候，这些操作就需要原子操作。就是不能被中断的操作。&lt;/p&gt;&#10;&lt;p&gt;硬件级的原子操作：在单处理器系统（UniProcessor）中，能够在单条指令中完成的操作都可以认为是“原子操作”，因为中断只发生在指令边缘。在多处理器结构中（Symmetric Multi-Processor）就不同了，由于系统中有多个处理器独立运行，即使能在单条指令中完成的操作也有可能受到干扰。在X86平台生，CPU提供了在指令执行期间对总线加锁的手段。CPU上有一根引线#HLOCK pin连到北桥，如果汇编语言的程序中在一条指令前面加上前缀”LOCK”，经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低，持续到这条指令结束时放开，从而把总线锁住，这样同一总线上别的CPU就暂时不能通过总线访问内存了，保证了这条指令在多处理器环境中的原子性。&lt;/p&gt;</description></item><item><title>xargs note</title><link>https://www.oneyearago.me/2019/06/23/xargs/</link><pubDate>Sun, 23 Jun 2019 15:40:39 +0800</pubDate><guid>https://www.oneyearago.me/2019/06/23/xargs/</guid><description>&lt;h3 id="作用"&gt;作用&lt;/h3&gt;&#10;&lt;p&gt;xargs，是execute arguments的缩写，它的作用是从标准输入中读取内容，并将&lt;br&gt;&#10;此内容传递给它要协助的命令，并作为那个命令的参数来执行。&lt;/p&gt;&#10;&lt;h3 id="与管道的区别"&gt;与管道的区别&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;管道可以实现：将前面的标准输出作为后面的 标准输入&lt;/li&gt;&#10;&lt;li&gt;管道无法实现：将前面的标准输出作为后面的 命令参数&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat /etc/passwd | grep root&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# grep命令可以接受标准输入作为参数&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;大多数命令都不接受标准输入作为参数，只能直接在命令行输入参数，这导致无法用管道命令传递参数&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;echo &amp;#34;one two three&amp;#34; | xargs mkdir&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# mkdir one two three&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="分隔符"&gt;分隔符&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;xargs会将前一个命令的标准输出转换成命令参数，但很多人可能不知道的是，xargs的标准输入中出现的“换行符、空格、制表符”都将被空格取代&lt;/li&gt;&#10;&lt;li&gt;xargs默认的分隔符是空格&lt;/li&gt;&#10;&lt;li&gt;还可以使用-d选项来指定任何一个符号作为分隔符&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="find-and-xargs"&gt;find and xargs&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find . -name &amp;#39;*.log&amp;#39; -print0 | xargs -0 rm -f&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;xargs提供了-0选项，允许将NULL作为分隔符，而find命令也心有灵犀地提供了对应的选项来产生以NULL字符作为分隔符的输出。&lt;/p&gt;&#10;&lt;p&gt;find命令提供的对应方法是-print0选项，在文件名之后输出NULL，而不像-print选项那样输出换行符（换行符会被xargs替换成空格）。&lt;/p&gt;&#10;&lt;p&gt;xargs的-0选项不仅可以将分隔符从默认的空格变成NULL，还会将单引号、双引号、反斜线等统统默认为是普通字符。所以说，-0选项特别适合处理命令参数中含有引号、空格、反斜线的情况.&lt;/p&gt;&#10;&lt;h3 id="other-options"&gt;other options&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;p 参数打印出要执行的命令，询问用户是否要执行&lt;/li&gt;&#10;&lt;li&gt;I 要将命令行参数传给多个命令&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat foo.txt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;one&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;two&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;three&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# -I file表示file是命令行参数的替代字符串&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat foo.txt | xargs -I file sh -c &amp;#39;echo file; mkdir file&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;one &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;two&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;three&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ls &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;one two three&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>江湖爱情故事</title><link>https://www.oneyearago.me/2019/05/28/history_and_love_of_peole/</link><pubDate>Tue, 28 May 2019 23:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/05/28/history_and_love_of_peole/</guid><description>&lt;p&gt;【潇湘夜雨 莫大先生】&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201905-1251969284.cossh.myqcloud.com/28-3-33343_1912.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;衡山派掌门人莫大先生一直没有婚娶。&lt;/p&gt;&#10;&lt;p&gt;有一回群雄聚会，刘正风忍不住问他：”师兄，你都一把年纪了，怎么还不娶妻生娃呢？”&lt;/p&gt;&#10;&lt;p&gt;莫大先生眼睛半开半闭，拉着他的胡琴，慢悠悠地说出一番话来：&lt;/p&gt;&#10;&lt;p&gt;要说成婚哪，首先得找个女人，对吧？&lt;/p&gt;&#10;&lt;p&gt;婚姻本来就是男人和女人的事。――东方不败脸色一沉，绣花的手倏然停下。&lt;/p&gt;&#10;&lt;p&gt;那么找个什么样的女人好呢？你要是找个难看的，这辈子心里都不平衡。――平一指四处打量，开始物色病人。&lt;/p&gt;&#10;&lt;p&gt;要是找个好看的呢，等着认一群襟兄襟弟吧。――吴三桂摸了摸鼻子。&lt;/p&gt;&#10;&lt;p&gt;有明着来抢的，――杨铁心默然不语。&lt;/p&gt;&#10;&lt;p&gt;有暗着来偷的，――苗人凤拍案而起。&lt;/p&gt;&#10;&lt;p&gt;有下个套算计你的，――狄云怒火中烧。&lt;/p&gt;&#10;&lt;p&gt;还有背着你不知干出些什么来的，――洪安通暴跳如雷。&lt;/p&gt;&#10;&lt;p&gt;甭管你是皇帝也好，――一灯大师数着念珠诵起经来。&lt;/p&gt;&#10;&lt;p&gt;是教主也罢，――阳顶天的脸青一阵红一阵。&lt;/p&gt;&#10;&lt;p&gt;绿帽子一戴就是没脾气。 ――殷梨亭顿足捶胸。&lt;/p&gt;&#10;&lt;p&gt;最好的结果，提心吊胆一辈子，到老也不得安宁。 ――陈正德恶狠狠地瞪着袁士霄。&lt;/p&gt;&#10;&lt;p&gt;这女人要是有点本事更不得了，一心只会争强斗胜。 ――胡青牛翻看着王难姑的《毒经》。&lt;/p&gt;&#10;&lt;p&gt;在她面前，你简直成了亚人类，――石清忍不住偷偷看了梅芳姑一眼。&lt;/p&gt;&#10;&lt;p&gt;把你当个牲口似地呼来喝去，――公孙止咬牙切齿。&lt;/p&gt;&#10;&lt;p&gt;你还不敢不服气。 ――何太冲面色如常，仿佛没有听见。&lt;/p&gt;&#10;&lt;p&gt;那些机灵的早早出家当道士去了。 ――王重阳连声咳嗽，好像把水呛进了气管。&lt;/p&gt;&#10;&lt;p&gt;剩下的只有打不还手的份。 ――谭公摸了摸怀里的药膏，放下心来。&lt;/p&gt;&#10;&lt;p&gt;没准搭错哪根筋，就一门心思和你作对。 ――白自在感到很不自在。&lt;/p&gt;&#10;&lt;p&gt;整死你你都猜不出是为了什么。 ――马大元魂魄归来，茫然若失。&lt;/p&gt;&#10;&lt;p&gt;再说感情吧，喜欢你的能把你吓死，――陆展元耳畔又响起李莫愁哀怨的歌声。&lt;/p&gt;&#10;&lt;p&gt;你喜欢的八辈子也追不到。 ――令狐冲抱起一个酒坛子，躲到墙角喝闷酒去了。&lt;/p&gt;&#10;&lt;p&gt;别以为给你个好脸你就有戏。 ――胡逸之若有所思。&lt;/p&gt;&#10;&lt;p&gt;别以为付出了就一定有回报。 ――游坦之面色惨白，仿佛结了一层冰。&lt;/p&gt;&#10;&lt;p&gt;你知道她是什么人吗？――胡斐百感交集。&lt;/p&gt;&#10;&lt;p&gt;你知道她做过什么吗？――张翠山感到无地自容。&lt;/p&gt;&#10;&lt;p&gt;你知道谁在背后利用她吗？――夏雪宜抚弄着手中的毒蛇。&lt;/p&gt;&#10;&lt;p&gt;你知道她对你安的什么心吗？――无尘道人看看自己断掉的左臂，发出一声极轻极轻的叹息。&lt;/p&gt;&#10;&lt;p&gt;好不容易找到个放心的，还有一群人死活要把你们拆散。 ――杨过与小龙女相顾无言。&lt;/p&gt;&#10;&lt;p&gt;当然那些主动弃权的火星人就不在此讨论了，咱今天只说地球人。 ――陈家洛痛不欲生。&lt;/p&gt;&#10;&lt;p&gt;说到底一句话：你感到幸福了吗？放心，那只一种错觉。 ――风清杨望着西斜的太阳，不知在想些什么。&lt;/p&gt;&#10;&lt;p&gt;更别说造化弄人了。 　　 　　有生离的，――张无忌想起小昭，心里就是一酸。&lt;/p&gt;&#10;&lt;p&gt;有死别的，――萧峰捏碎了酒杯，刺得自己满手是血。&lt;/p&gt;&#10;&lt;p&gt;有离家出走的，――不戒和尚目露凶光。&lt;/p&gt;&#10;&lt;p&gt;有岳父难缠的，――丁典心如刀搅。&lt;/p&gt;&#10;&lt;p&gt;有生孩子难产的，――黄药师的玉箫吹出杀伐之音。&lt;/p&gt;&#10;&lt;p&gt;生下孩子你也别得意，先看看像不像你，――钟万仇破口大骂。&lt;/p&gt;&#10;&lt;p&gt;像你也不一定能养大，――谢逊手按屠龙刀柄。&lt;/p&gt;&#10;&lt;p&gt;养大了也不一定健康，――归辛树怒不可遏。&lt;/p&gt;&#10;&lt;p&gt;健康的少不了到处闯祸，――郭靖横了郭芙一眼，郭芙马上躲到门外去了。&lt;/p&gt;&#10;&lt;p&gt;见到个女人连姓什么都忘了，――宋远桥泫然泣下。&lt;/p&gt;&#10;&lt;p&gt;亲兄弟也拼个你死我活，――武三通悲从中来，忍不住放声大哭。&lt;/p&gt;&#10;&lt;p&gt;到头来白发人送黑发人，――欧阳锋\”腾\”地倒立起来。&lt;/p&gt;&#10;&lt;p&gt;让你一世英名毁于一旦。 ――刘正风面色大变。&lt;/p&gt;&#10;&lt;p&gt;师弟，你说，为什么要娶妻，为什么要生娃？啊？你说。&lt;/p&gt;&#10;&lt;p&gt;众人沉默半晌，一齐围上前来，狂殴莫大。。。&lt;/p&gt;</description></item><item><title>写一个操作系统_15 内存管理初步</title><link>https://www.oneyearago.me/2019/04/27/make_a_linux_15/</link><pubDate>Sat, 27 Apr 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/04/27/make_a_linux_15/</guid><description>&lt;h3 id="内存管理初步"&gt;内存管理初步&lt;/h3&gt;&#10;&lt;h4 id="物理内存"&gt;物理内存&lt;/h4&gt;&#10;&lt;p&gt;认为的先规划成两部分，用户的物理内存和内核的物理内存，专项专用，内核有方法去占用用户的内存，但是规定两部分内存是专用的，内核只使用自己的物理内存。&lt;/p&gt;&#10;&lt;p&gt;物理内存的内核、用户部分分别用 bitmap 来统计管理。&lt;/p&gt;&#10;&lt;h4 id="虚拟内存"&gt;虚拟内存&lt;/h4&gt;&#10;&lt;p&gt;首先，虚拟内存也是需要管理的，毕竟程序要申请堆上的内存，假定我们只支持512M物理内存，那么我们只要用 4* 4K 就能 记录这512M。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201906-1251969284.cossh.myqcloud.com/bmp_kernel.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;备注: 把位图的地址放在这个地方是人为选择，也可以选其他地方&lt;/p&gt;&#10;&lt;p&gt;同样，我们把 内核堆 开始地址 放到 0xc0100000，当然你也可以放到更远。另外，注意到我们的页表就在物理地址1M以上，我们需要把页表的内存算作已经使用的内存。&lt;/p&gt;&#10;&lt;p&gt;/ &lt;em&gt;0xc0000000是内核从虚拟地址3G起. 0x100000意指跨过低端1M内存,使虚拟地址在逻辑上连续&lt;/em&gt; /&lt;/p&gt;&#10;&lt;p&gt;#define K_HEAP_START 0xc0100000&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/* 初始化内存池 */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;static void mem_pool_init(uint32_t all_mem) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; put_str(&amp;#34; mem_pool_init start\n&amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint32_t page_table_size = PG_SIZE * 256;&#9; // 页表大小= 1页的页目录表+第0和第768个页目录项指向同一个页表+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; // 第769~1022个页目录项共指向254个页表,共256个页框&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint32_t used_mem = page_table_size + 0x100000;&#9; // 0x100000为低端1M内存&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint32_t free_mem = all_mem - used_mem;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint16_t all_free_pages = free_mem / PG_SIZE;&#9;&#9; // 1页为4k,不管总内存是不是4k的倍数,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9; // 对于以页为单位的内存分配策略，不足1页的内存不用考虑了。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint16_t kernel_free_pages = all_free_pages / 2;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint16_t user_free_pages = all_free_pages - kernel_free_pages;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/* 为简化位图操作，余数不处理，坏处是这样做会丢内存。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;好处是不用做内存的越界检查,因为位图表示的内存少于实际物理内存*/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint32_t kbm_length = kernel_free_pages / 8;&#9;&#9;&#9; // Kernel BitMap的长度,位图中的一位表示一页,以字节为单位&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint32_t ubm_length = user_free_pages / 8;&#9;&#9;&#9; // User BitMap的长度.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint32_t kp_start = used_mem;&#9;&#9;&#9;&#9; // Kernel Pool start,内核内存池的起始地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE;&#9; // User Pool start,用户内存池的起始地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kernel_pool.phy_addr_start = kp_start;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; user_pool.phy_addr_start = up_start;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kernel_pool.pool_size = kernel_free_pages * PG_SIZE;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; user_pool.pool_size&#9; = user_free_pages * PG_SIZE;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; user_pool.pool_bitmap.btmp_bytes_len&#9; = ubm_length;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/********* 内核内存池和用户内存池位图 ***********&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; * 位图是全局的数据，长度不固定。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; * 全局或静态的数组需要在编译时知道其长度，&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; * 而我们需要根据总内存大小算出需要多少字节。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; * 所以改为指定一块内存来生成位图.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; * ************************************************/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// 内核使用的最高地址是0xc009f000,这是主线程的栈地址.(内核的大小预计为70K左右)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// 32M内存占用的位图是2k.内核内存池的位图先定在MEM_BITMAP_BASE(0xc009a000)处.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kernel_pool.pool_bitmap.bits = (void*)MEM_BITMAP_BASE;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&#9;&#9;&#9; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/* 用户内存池的位图紧跟在内核内存池位图之后 */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; user_pool.pool_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /* 将位图置0*/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bitmap_init(&amp;amp;kernel_pool.pool_bitmap);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bitmap_init(&amp;amp;user_pool.pool_bitmap);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /* 下面初始化内核虚拟地址的位图,按实际物理内存大小生成数组。*/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length; // 用于维护内核堆的虚拟地址,所以要和内核内存池大小一致&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /* 位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之外*/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kernel_vaddr.vaddr_bitmap.bits = (void*)(MEM_BITMAP_BASE + kbm_length + ubm_length);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; kernel_vaddr.vaddr_start = K_HEAP_START;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bitmap_init(&amp;amp;kernel_vaddr.vaddr_bitmap);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="分配物理内存"&gt;分配物理内存&lt;/h4&gt;&#10;&lt;p&gt;malloc分配了虚拟地址，也会为该虚拟地址物理内存。linux的实现上，分配物理地址不是实时的，这里先假设是实时的，并且每次分配是按照页为单位。 关键的一点，分配的物理内存和分配的虚拟内存 要通过 页表 关联上。&lt;/p&gt;</description></item><item><title>写一个操作系统_14 C 链接与系统调用</title><link>https://www.oneyearago.me/2019/04/24/make_a_linux_14/</link><pubDate>Wed, 24 Apr 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/04/24/make_a_linux_14/</guid><description>&lt;h3 id="用C语言写内核"&gt;用C语言写内核&lt;/h3&gt;&#10;&lt;p&gt;无论什么语言，要编译成 ELF文件格式(或者定一个其他的标准)&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int main()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; while(1){};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;链接可以指定最终生成的可执行文件的起始虚拟地址，我们 指定 内核加载到 0x1500的地方，内核初始化的时候跳转内核要跳转到这个地方。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ld kernel/main.o -Ttext Oxc0001500 -e main -o kernel/kernel.bin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$$ ~&amp;gt;ld -help entry&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Usage: ld [options] file...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Options:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e ADDRESS, --entry ADDRESS Set start address&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="加载并执行-ELF-kernrl"&gt;加载并执行 ELF kernrl&lt;/h3&gt;&#10;&lt;p&gt;把编译完的内核代码加载到内存，分两步:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;加载 ELF文件到内存&lt;/li&gt;&#10;&lt;li&gt;根据 ELF 文件格式 初始化 kernel ，链接的时候指定了 入口点，loader初始化内核后跳转就可以了&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="调试"&gt;调试&lt;/h3&gt;&#10;&lt;p&gt;我们把 MBR , loader的代码 放到 虚拟硬盘的前两个扇区， 同样的，我们把 编译完的 kernel 的二进制写到 磁盘的第9扇区后的200个扇区，不超过100k, loader里面 先拷贝 磁盘内容到 内存，初始化内核后，调到 内核入口点&lt;/p&gt;</description></item><item><title>写一个操作系统_13 内核的宏观视角</title><link>https://www.oneyearago.me/2019/04/23/make_a_linux_13/</link><pubDate>Tue, 23 Apr 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/04/23/make_a_linux_13/</guid><description>&lt;h3 id="规划内核的内存分布"&gt;规划内核的内存分布&lt;/h3&gt;&#10;&lt;h4 id="MBR、loader部分"&gt;MBR、loader部分&lt;/h4&gt;&#10;&lt;p&gt;我们把 加MBR、Loader以及后面需要加载的内核都放到 物理内存的 0-1M 这个空间里面，其中，MBR执行完了以后，我们把它覆盖掉，如下面的图。&lt;/p&gt;&#10;&lt;p&gt;图1: MBR,loader的物理内存定位&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201906-1251969284.cossh.myqcloud.com/kerner_step_1.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;图二: 覆盖MBR位置写kerner.bin&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201906-1251969284.cossh.myqcloud.com/kerner_step_2.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="分离用户空间和内核空间"&gt;分离用户空间和内核空间&lt;/h4&gt;&#10;&lt;p&gt;用保护模式平坦模式寻址方式，0-3G是用户空间，3G-4G是内核空间。&lt;/p&gt;&#10;&lt;p&gt;对于内核代码段和数据段而言，段基址的值是0xC00000000&lt;/p&gt;&#10;&lt;p&gt;而用户代码和数据段的段基址的值是0x00000000&lt;/p&gt;&#10;&lt;h4 id="二级页表设计"&gt;二级页表设计&lt;/h4&gt;&#10;&lt;p&gt;物理地址 0-1M 打算放内核有关的逻辑了，紧挨着1M往上，把页目录和页表放在这里&lt;br&gt;&#10;&lt;img src="http://201906-1251969284.cossh.myqcloud.com/kernel_page.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;页表项和目录项都是32bit,记录一个页的地址只需要20位，其余的12bit用作标记位。&lt;br&gt;&#10;&lt;img src="http://201906-1251969284.cossh.myqcloud.com/kernel_page_desc.png" alt="image"&gt;&lt;/li&gt;&#10;&lt;li&gt;按照内存规划，把虚拟 3G - 4G 内存映射到 物理0-1M 内存上&lt;/li&gt;&#10;&lt;li&gt;最后一个页目录项填入了页目录表的物理地址，方便后续使用虚拟地址访问页表&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="涉及的寄存器"&gt;涉及的寄存器&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;CR0&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;0位是保护允许位PE(Protedted Enable)，用于启动保护模式，如果PE位置1，则保护模式启动，如果PE=0，则在实模式下运行。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;CR3&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;页目录基址寄存器，保存页目录表的物理地址，页目录表总是放在以4K字节为单位的存储器边界上。&lt;/p&gt;&#10;&lt;h3 id="code"&gt;code&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;------------- 创建页目录及页表 ---------------&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;setup_page:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;先把页目录占用的空间逐字节清0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ecx, 4096&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov esi, 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.clear_page_dir:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov byte [PAGE_DIR_TABLE_POS + esi], 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; inc esi&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; loop .clear_page_dir&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;开始创建页目录项(PDE)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.create_pde:&#9;&#9;&#9;&#9; ; 创建Page Directory Entry&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov eax, PAGE_DIR_TABLE_POS&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; add eax, 0x1000 &#9;&#9;&#9; ; 此时eax为第一个页表的位置及属性&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ebx, eax&#9;&#9;&#9;&#9; ; 此处为ebx赋值，是为.create_pte做准备，ebx为基址。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 下面将页目录项0和0xc00都存为第一个页表的地址，&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 一个页表可表示4MB内存,这样0xc03fffff以下的地址和0x003fffff以下的地址都指向相同的页表，&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 这是为将地址映射为内核地址做准备&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; or eax, PG_US_U | PG_RW_W | PG_P&#9; ; 页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov [PAGE_DIR_TABLE_POS + 0x0], eax ; 第1个目录项,在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性(7)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov [PAGE_DIR_TABLE_POS + 0xc00], eax ; 一个页表项占用4字节,0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&#9; ; 也就是页表的0xc0000000~0xffffffff共计1G属于内核,0x0~0xbfffffff共计3G属于用户进程.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sub eax, 0x1000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov [PAGE_DIR_TABLE_POS + 4092], eax&#9; ; 使最后一个目录项指向页目录表自己的地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;下面创建页表项(PTE)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ecx, 256&#9;&#9;&#9;&#9; ; 1M低端内存 / 每页大小4k = 256&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov esi, 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov edx, PG_US_U | PG_RW_W | PG_P&#9; ; 属性为7,US=1,RW=1,P=1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.create_pte:&#9;&#9;&#9;&#9; ; 创建Page Table Entry&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov [ebx+esi*4],edx&#9;&#9;&#9; ; 此时的ebx已经在上面通过eax赋值为0x101000,也就是第一个页表的地址 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; add edx,4096 ; edx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; inc esi&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; loop .create_pte&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;创建内核其它页表的PDE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov eax, PAGE_DIR_TABLE_POS&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; add eax, 0x2000 &#9;&#9; ; 此时eax为第二个页表的位置&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; or eax, PG_US_U | PG_RW_W | PG_P ; 页目录项的属性US,RW和P位都为1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ebx, PAGE_DIR_TABLE_POS&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ecx, 254&#9;&#9;&#9; ; 范围为第769~1022的所有目录项数量&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov esi, 769&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.create_kernel_pde:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov [ebx+esi*4], eax&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; inc esi&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; add eax, 0x1000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; loop .create_kernel_pde&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ret&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>写一个操作系统_12 linux 原始的几个进程</title><link>https://www.oneyearago.me/2019/04/20/make_a_linux_12/</link><pubDate>Sat, 20 Apr 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/04/20/make_a_linux_12/</guid><description>&lt;h3 id="原始进程"&gt;原始进程&lt;/h3&gt;&#10;&lt;p&gt;Linux下有3个特殊的进程，idle进程(PID = 0), init进程(PID = 1)和kthreadd(PID = 2)&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;idle进程&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;由系统自动创建, 运行在内核态&lt;/p&gt;&#10;&lt;p&gt;idle进程其pid=0，其前身是系统创建的第一个进程，也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后，演变为进程调度&lt;/p&gt;&#10;&lt;p&gt;调用schedule()函数切换当前进程，在调用该函数之前，Linux系统中只有两个进程，即0号进程init_task和1号进程kernel_init，其中kernel_init进程也是刚刚被创建的。调用该函数后，1号进程kernel_init将会运行！&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;init进程&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;由idle通过kernel_thread创建，在内核空间完成初始化后, 加载init程序, 并最终转向用户空间，演变为init进程&lt;br&gt;&#10;由0进程创建，完成系统的初始化. 是系统中所有其它用户进程的祖先进程&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;kthreadd进程&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;由idle通过kernel_thread创建，并始终运行在内核空间, 负责所有内核线程的调度和管理&lt;/p&gt;&#10;&lt;p&gt;它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthread的函数，该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中，因此所有的内核线程都是直接或者间接的以kthreadd为父进程&lt;/p&gt;&#10;&lt;h3 id="启动初始化进程"&gt;启动初始化进程&lt;/h3&gt;&#10;&lt;p&gt;内核文件加载以后，就开始运行第一个程序 /sbin/init，它的作用是初始化系统环境。&lt;/p&gt;&#10;&lt;p&gt;由于init是第一个运行的程序，它的进程编号（pid）就是1。其他所有进程都从它衍生，都是它的子进程。&lt;/p&gt;&#10;&lt;h3 id="确定运行级别"&gt;确定运行级别&lt;/h3&gt;&#10;&lt;p&gt;许多程序需要开机启动。它们在Windows叫做”服务”（service），在Linux就叫做”守护进程”（daemon）。&lt;/p&gt;&#10;&lt;p&gt;init进程的一大任务，就是去运行这些开机启动的程序。但是，不同的场合需要启动不同的程序，比如用作服务器时，需要启动Apache，用作桌面就不需要。Linux允许为不同的场合，分配不同的开机启动程序，这就叫做”运行级别”（runlevel）。也就是说，启动时根据”运行级别”，确定要运行哪些程序。&lt;/p&gt;&#10;&lt;p&gt;Linux预置七种运行级别（0-6）。一般来说，0是关机，1是单用户模式（也就是维护模式），6是重启。运行级别2-5，各个发行版不太一样，对于Debian来说，都是同样的多用户模式（也就是正常模式）。&lt;/p&gt;&#10;&lt;p&gt;init进程首先读取文件 /etc/inittab，它是运行级别的设置文件。initdefault的值是2，表明系统启动时的运行级别为2。&lt;/p&gt;&#10;&lt;h3 id="加载开机启动程序"&gt;加载开机启动程序&lt;/h3&gt;&#10;&lt;p&gt;那么，运行级别2有些什么程序呢，系统怎么知道每个级别应该加载哪些程序呢？……回答是每个运行级别在/etc目录下面，都有一个对应的子目录，指定要加载的程序。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/etc/rc0.d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;　　/etc/rc1.d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;　　/etc/rc2.d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;　　/etc/rc3.d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;　　/etc/rc4.d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;　　/etc/rc5.d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;　　/etc/rc6.d&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="http://201905-1251969284.cossh.myqcloud.com/linux_init.png" alt="image"&gt;&lt;/p&gt;</description></item><item><title>写一个操作系统_11 Linux是怎么实现线程的</title><link>https://www.oneyearago.me/2019/04/11/make_a_linux_11/</link><pubDate>Thu, 11 Apr 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/04/11/make_a_linux_11/</guid><description>&lt;h4 id="Linux2-6以前的线程"&gt;Linux2.6以前的线程&lt;/h4&gt;&#10;&lt;p&gt;在Linux内核2.6出现之前进程是(最小)可调度的对象，当时的Linux不真正支持线程。Linux 2.4内核中不知道什么是“线程”，只有一个“task_struct”的数据结构，就是进程。&lt;/p&gt;&#10;&lt;p&gt;Linux内核有一个系统调用指令clone()，这个指令产生一个调用调用的进程的复件，而且这个复件与原进程使用同一地址空间。LinuxThreads计划使用这个系统调用来提供一个内核级的线程支持。但是这个解决方法与真正的POSIX标准有一些不兼容的地方，尤其是在信号处理、进程调度和进程间同步原语方面。&lt;/p&gt;&#10;&lt;h4 id="Native-POSIX-Thread-Library"&gt;Native POSIX Thread Library&lt;/h4&gt;&#10;&lt;p&gt;一个操作系统比较全面的支持线程是需要改内核的，怎么干改内核这个艰苦卓越的工作？Linux是开源、免费的，谁愿意来干这个活？&lt;/p&gt;&#10;&lt;p&gt;有两家公司参与了对LinuxThreads的改进（向他们致敬）：IBM启动的NGTP(Next Generation POSIX Threads)项目，以及红帽Redhat公司的NPTL（Native POSIX Thread Library），IBM那个项目，在2003年因为种种原因放弃了，大家都转到NPTL这个项目来了。&lt;/p&gt;&#10;&lt;p&gt;因为Linux一开始就决定在进程的结构上支持线程，线程和进程都共用task_struct这个结构，所以这个设计一直延续了下来。所以说Linux下通过NPTL创建的线程是内核线程，他会在内核创建一个线程结构供处理器调度，也就是所谓的1:1模型。&lt;/p&gt;&#10;&lt;h4 id="用户级线程"&gt;用户级线程&lt;/h4&gt;&#10;&lt;p&gt;举个例子，在收发网络包的时候，recv可以是阻塞的，这个时候如果没有网络回包，正在运行的进程会阻塞，然后这个正在运行的pthread 会从CPU上下来，调度其他的线程。&lt;/p&gt;&#10;&lt;p&gt;由于上面的问题，我们希望我们的程序不因为IO而终止运行，于是产生了用户级线程，我们在用户程序部分定义自己的线程(执行流)，在线程阻塞之前主动切换。&lt;/p&gt;&#10;&lt;p&gt;对Linux来说，用户级的线程其实还是跑在pthread上面的，其中有两种模型比较出名:&lt;br&gt;&#10;M:1 : M个用户级线程跑在1个内核线程上，俗称 协程&lt;br&gt;&#10;M:N : M个用户级线程跑在N个内核级线程上，golang和baidu-rpc的做法&lt;/p&gt;&#10;&lt;p&gt;当然，这部分不是Linux做的，用户程序可以在内核线程的支持下实现&lt;/p&gt;&#10;&lt;h4 id="参考资料"&gt;参考资料&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://akkadia.org/drepper/nptl-design.pdf"&gt;The Native POSIX Thread Library for Linux&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.linuxjournal.com/article/6530"&gt;Introducing the 2.6 Kernel&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>写一个操作系统_10 操作系统是由中断驱动的</title><link>https://www.oneyearago.me/2019/04/08/make_a_linux_10/</link><pubDate>Mon, 08 Apr 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/04/08/make_a_linux_10/</guid><description>&lt;h3 id="中断分类"&gt;中断分类&lt;/h3&gt;&#10;&lt;p&gt;按照中断的来源分类，来自CPU外部的中断称为外部中断，来自CPU内部的称为内部中断；细分的话，外部中断根据严重程度，分为可屏蔽中断和不可屏蔽中断；内部中断按照是否正常分为软中断和异常。&lt;/p&gt;&#10;&lt;h3 id="外部中断"&gt;外部中断&lt;/h3&gt;&#10;&lt;p&gt;外部中断来自CPU的外部的某个硬件，因此又被称为硬件中断，比如网卡收到了一个数据包，它就会通知CPU，CPU得到通知便将数据拷贝到内核缓冲区。&lt;/p&gt;&#10;&lt;p&gt;为了接受外部中断，CPU有两个引脚作为接受接口，INTR和NMI线。&lt;/p&gt;&#10;&lt;h4 id="可屏蔽中断"&gt;可屏蔽中断&lt;/h4&gt;&#10;&lt;p&gt;有些中断是不会引起系统宕机，还是网卡的例子，网络数据包通过网线达到网卡，存到网卡自己的缓冲区，这个缓冲区是会被写满的，写满以后，后来的数据就会被丢掉。写满以后，CPU收到中断一般会马上拷贝网卡数据到内核缓冲区，但是这样的数据其实是可以丢的，不会引发崩溃，属于可屏蔽中断。&lt;/p&gt;&#10;&lt;p&gt;这类中断通过INTR线传到CPU引脚。&lt;/p&gt;&#10;&lt;h4 id="不可屏蔽中断"&gt;不可屏蔽中断&lt;/h4&gt;&#10;&lt;p&gt;内存读写错误这类的属于不可屏蔽，出了问题只能找硬件工程师了 : )&lt;/p&gt;&#10;&lt;h3 id="内部中断"&gt;内部中断&lt;/h3&gt;&#10;&lt;p&gt;内部中断可以分为软中断和异常。&lt;/p&gt;&#10;&lt;h4 id="软中断"&gt;软中断&lt;/h4&gt;&#10;&lt;p&gt;软中断就是由软件主动引发的中断，可以认为是主动引发的，从而实现在CPU的支持下实现某种功能。比如:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;int 0xxx 表示进行系统调用&lt;/li&gt;&#10;&lt;li&gt;int3 我们用gdb 或bochs 调试程序时，实际上就是调试器fork 了一个子进程，&lt;br&gt;&#10;子进程用于运行被调试的程序。调试器中经常要设置断点，其原理就是父进程修改了子进程的指令,触发3号中断，执行3号中断上的中断处理程序。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="异常"&gt;异常&lt;/h4&gt;&#10;&lt;p&gt;发生除0，缺页等错误的时候，正常的流程被打断。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;可被修复的异常 比如操作系统的缺页异常&lt;/li&gt;&#10;&lt;li&gt;终止程序的异常&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="中断描述符"&gt;中断描述符&lt;/h3&gt;&#10;&lt;p&gt;一个中断源就会产生一个中断向量，每个中断向量都对应中断描述符表中的一个门描述符，任何中断&lt;br&gt;&#10;源都通过中断向量对应到中断描述符表中的门描述符，通过该门描述符就找到了对应的中断处理程序。&lt;/p&gt;&#10;&lt;p&gt;在CPU 内部有个中断描述符表寄存器（ Interrupt Descriptor Table Register, IDTR ），该寄存器分为两&lt;br&gt;&#10;部分：第O～ 15 位是表界限，第16～47 位是IDT 的基地址。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/idtr.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="中断时候的栈变化"&gt;中断时候的栈变化&lt;/h4&gt;&#10;&lt;p&gt;当前进程被中断打断后，为了从中断返回后能继续运行该进程，处理器自&lt;br&gt;&#10;动把CS 和EIP 的当前值保存到中断处理程序使用的栈中。&lt;/p&gt;&#10;&lt;p&gt;不同特权级别下处理器使用不同的栈，至于中断处&lt;br&gt;&#10;理程序使用的是哪个栈，要视它当时所在的特权级别，因为中断是可以在任何特权级别下发生的。&lt;/p&gt;&#10;&lt;p&gt;除了要保存CS 、EIP 外，还需要保存标志寄存器EFLAGS ，如果涉及到特权级变化，还要压入SS 和ESP 寄存器。&lt;/p&gt;</description></item><item><title>2019年4月</title><link>https://www.oneyearago.me/2019/04/07/201904/</link><pubDate>Sun, 07 Apr 2019 22:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/04/07/201904/</guid><description>&lt;h3 id="2019年4月"&gt;2019年4月&lt;/h3&gt;&#10;&lt;h5 id="上交拜访颢C"&gt;上交拜访颢C&lt;/h5&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190403_175550.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190403_220104.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="和大学室友爬黄山"&gt;和大学室友爬黄山&lt;/h5&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190405_072816.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190405_073019.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190405_080959.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190405_111210.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190405_124335.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="黄山周边"&gt;黄山周边&lt;/h5&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190406_102123.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190406_102244.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/IMG_20190406_104635.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="遇到一个可爱的小妹妹"&gt;遇到一个可爱的小妹妹 :)&lt;/h5&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/mmexport1554423257608.jpg" alt="image"&gt;&lt;/p&gt;</description></item><item><title>写一个操作系统_09 C语言和汇编语言</title><link>https://www.oneyearago.me/2019/04/02/make_a_linux_9/</link><pubDate>Tue, 02 Apr 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/04/02/make_a_linux_9/</guid><description>&lt;h4 id="函数调用约定"&gt;函数调用约定&lt;/h4&gt;&#10;&lt;p&gt;编译器一般使用堆栈实现函数调用，每个进程都有自己的栈，用栈来传递参数，会带来以下问题：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;参数存储在栈中，那么谁来负责回收参数所占的栈空间，是调用者？还是被调用者&lt;/li&gt;&#10;&lt;li&gt;当参数很多的时候后，主调函数将参数以什么样的顺序传递&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;高级语言有调用的约定:&lt;br&gt;&#10;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/func_call_types.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;举例:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int sub(int a , int b){ // 被调&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return a-b;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int s = sub(3,2); // 调用&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;调用:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;push 2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;push 3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;call sub&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;被调:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;push ebp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mov ebp,esp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mov eax,[ebp+8]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sub eax,[ebp+12]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pop ebp // 恢复 ebp&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="C-amp-ASM-混合"&gt;C &amp;amp; ASM 混合&lt;/h4&gt;&#10;&lt;p&gt;C和汇编混合编程有两种方式:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;用汇编和C单独写单独编译，然后链接在一起&lt;/li&gt;&#10;&lt;li&gt;C代码嵌入汇编&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;C代码嵌入汇编:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;char* str = &amp;#34;C &amp;amp; ASM Code\n&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int count = 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int main()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; asm(&amp;#34;pusha;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; movl $4,%eax;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; movl $1,%ebx;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; movl str,%ecx;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; movl $13,%edx;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int $0x80;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; movl %eax,count;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; popa;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// gcc -m32 casm.c&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="内联汇编及其扩展"&gt;内联汇编及其扩展&lt;/h4&gt;&#10;&lt;p&gt;上面的例子是内联汇编的一个例子，基本的内敛汇编功能比较薄弱，在此基础上，GCC添加了扩展功能，在C语言里面用扩展汇编真的不舒服，相当于多了一种语言，需要依据其规则。&lt;/p&gt;</description></item><item><title>文件描述符限制</title><link>https://www.oneyearago.me/2019/03/30/file_handler_limit/</link><pubDate>Sat, 30 Mar 2019 16:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/03/30/file_handler_limit/</guid><description>&lt;h3 id="文件描述符限制"&gt;文件描述符限制&lt;/h3&gt;&#10;&lt;h4 id="文件描述符"&gt;文件描述符&lt;/h4&gt;&#10;&lt;p&gt;Linux的核心思想之一就是”一切皆文件”。即Linux中所有的内容都是以文件的形式保存和管理的，它为不同类型的文件提供了统一的操作接口，对于不同类型的文件，我们都可以使用fopen()/fclose()/fwrite()/fread()等对这些文件进行读写处理。在Linux中，普通文件、目录、链接文件、字符设备、块设备以及网络套接字等等都以文件的形式存在。&lt;/p&gt;&#10;&lt;p&gt;文件描述符（文件句柄）是Linux内核为了高效管理已被打开的文件所创建的索引，它是一个非负整数，用于指代被打开的文件。文件打开后，所有对该文件的操作都通过文件描述符进行操作。&lt;/p&gt;&#10;&lt;p&gt;文件描述符是系统的重要资源，当打开过多文件时会出现把系统内存耗尽的情况，所以Linux提供了多种维度对文件描述符的使用上限进行限制，以避免该问题。&lt;/p&gt;&#10;&lt;h4 id="文件描述符限制-1"&gt;文件描述符限制&lt;/h4&gt;&#10;&lt;h5 id="file-max"&gt;file-max&lt;/h5&gt;&#10;&lt;p&gt;file_max用于限制整个系统能够分配的文件描述符，即当前系统下所有用户下的所有进程，其分配的所有文件描述符之和不能超过该值。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dailybuild@9.77.10.46:~&amp;gt;cat /proc/sys/fs/file-max &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3043564&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 临时修改，机器重启后失效&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sysctl -w fs.file-max=102400&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 永久修改（直接在/etc/sysctl.conf文件最后添加一行fs.file-max=102400），执行下面命令使配置生效。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sysctl -p&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id="file-nr"&gt;file-nr&lt;/h5&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dailybuild@9.77.10.46:~&amp;gt;cat /proc/sys/fs/file-nr&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1952 0 3043564&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;file-nr是一个只读文件,它记录了当前系统中文件描述符的使用情况，第一个（6144）是系统已经分配的文件描述符数；第二个（0）是系统已经分配但未使用的文件描述符数；第三个是内核最大能分配的文件描述符数，与file-max一致。我们可以通过查看此数据判断文件描述符是够已经将要用尽和是否需要扩file-max字段。&lt;/p&gt;&#10;&lt;h5 id="nr-open"&gt;nr_open&lt;/h5&gt;&#10;&lt;p&gt;nr_open用于限制单个进程可以分配的最大文件描述符，该限制对于系统上所有用户下的所有进程都生效，查看当时如下：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dailybuild@9.77.10.46:~&amp;gt;cat /proc/sys/fs/nr_open&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1048576&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id="soft-limit-amp-amp-hard-limit"&gt;soft limit &amp;amp;&amp;amp; hard limit&lt;/h5&gt;&#10;&lt;p&gt;Soft limit 和 Hard limit# 在Linux的系统中对于进程(Process)会有一些限制，这就所谓的limit，在实际应用中最常见的就是对打开文件(Open Files)的限制，在配置web 服务如nginx时就会用到。在linux中这些限制是分为软限制(soft limit)和硬限制(hard limit)的。他们的区别就是软限制可以在程序的进程中自行改变(突破限制)，而硬限制则不行(除非程序进程有root权限)&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ulimit -Sn 查看的是软限制&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ulimit -Hn 查看的是硬限制&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在这个文件中我们可以对cpu, 内存等等设置限制&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo vim /etc/security/limit.conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>写一个操作系统_08 内核与C语言</title><link>https://www.oneyearago.me/2019/03/29/make_a_linux_8/</link><pubDate>Fri, 29 Mar 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/03/29/make_a_linux_8/</guid><description>&lt;h3 id="内核与C语言"&gt;内核与C语言&lt;/h3&gt;&#10;&lt;h4 id="写内核能用C标准库吗？"&gt;写内核能用C标准库吗？&lt;/h4&gt;&#10;&lt;p&gt;我们开发用户应用程序的时候，有标准库可以用，最典型的就是GUN C库，标准库一般是系统调用的封装，表面上是通过标准库访问系统资源，实际上是通过系统调用实现的。Linux的系统调用一般是先往eax寄存器写入系统调用号，然后通过0x80中断来实现。中断向量号为0x80称为系统中断门，更多的中断参考中断描述符表。&lt;/p&gt;&#10;&lt;p&gt;所以自己写内核的话可以用C语言，毕竟可以编译成汇编，跟直接写汇编差不多，但是不能用C标准库，理由如上。&lt;/p&gt;&#10;&lt;h4 id="编译32位程序"&gt;编译32位程序&lt;/h4&gt;&#10;&lt;p&gt;默认的GCC编译选项会编译出与系统一致的输出，例如64位直接使用gcc或g++编译出的为64位程序或库，而32位系统编译的是对应32位的。&lt;br&gt;&#10;可以通过在编译时添加选项-m32或-m64来指定编译生成的相应版本。&lt;/p&gt;&#10;&lt;p&gt;问题的关键时多数时候会提示缺少库，这里以64位下编译32位程序为例，给出ubuntu和centos下相关依赖包。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-get install build-essential module-assistant&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-get install gcc-multilib g++-multilib&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;yum install glibc-devel.i686 libstdc++-devel.i686&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="C运行时库"&gt;C运行时库&lt;/h4&gt;&#10;&lt;p&gt;C运行时库与标准库不一样，可以理解为与操作系统无关的部分的汇编封装。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// 32b.c &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int main()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; while(1);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; return 0;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# gcc -c -o m32b.o 32b.c &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ld m32b.o -Ttext 0xc0001500 -o m32.bin // 默认链接 c-runtime-lib crt.o _start &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ld: warning: cannot find entry symbol _start; defaulting to 00000000c0001500&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ld m32b.o -Ttext 0xc0001500 -e main -o m32.bin # 指定开始的地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="ELF文件"&gt;ELF文件&lt;/h4&gt;&#10;&lt;p&gt;ELF是Linux上的可执行文件，其中Entry point address相当于操作系统与文件的约定，操作系统加载这个可执行文件的时候，去这个地方作为入口点。&lt;/p&gt;</description></item><item><title>写一个操作系统_07 设计自己的页表</title><link>https://www.oneyearago.me/2019/03/28/make_a_linux_7/</link><pubDate>Thu, 28 Mar 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/03/28/make_a_linux_7/</guid><description>&lt;h3 id="设计自己的页表"&gt;设计自己的页表&lt;/h3&gt;&#10;&lt;p&gt;只针对32位的操作系统，设计一个二级页表，目的是构建一个简易的能跑起来的操作系统。对于4G的地址空间，每个页大小是4K,模仿Linux早期的做法，32位地址的前10位为页目录项，中间10位为页表，后面10位为偏移量。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|--目录项----|--页表-----|---地址偏移-|&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;|---10bit----|---10bit---|----12bit---|&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="总体布局"&gt;总体布局&lt;/h4&gt;&#10;&lt;p&gt;我们把我们的内核放在物理地址1M以内，页表放在物理地址1M的地方，即 0x100000,目录项占 1K * 4B = 4K,那么页表在 0x101000的地方。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/page_phy_addr.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;备注:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;我们模仿Linux，0-3G是用户空间，3G-4G是内核空间，又因为咱们写死了内核加载到物理地址1M以内，所以将0xc0000000 映射到物理地址的0-4M上&lt;/li&gt;&#10;&lt;li&gt;第一个页表也映射到0-4M这个空间，我们的内核用了这个地方，方便操作&lt;/li&gt;&#10;&lt;li&gt;最后一个目录项指向页目录表自己的地址，为了后面进程页表操作方便&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;setup_page:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;先把页目录占用的空间逐字节清0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ecx, 4096&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov esi, 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.clear_page_dir:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov byte [PAGE_DIR_TABLE_POS + esi], 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; inc esi&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; loop .clear_page_dir&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;开始创建页目录项(PDE)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.create_pde:&#9;&#9;&#9;&#9; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 创建Page Directory Entry&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov eax, PAGE_DIR_TABLE_POS&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; add eax, 0x1000 &#9;&#9;&#9; ; 此时eax为第一个页表的位置及属性&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ebx, eax&#9;&#9;&#9;&#9; ; 此处为ebx赋值，是为.create_pte做准备，ebx为基址。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 下面将页目录项0和0xc00都存为第一个页表的地址，&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 一个页表可表示4MB内存,这样0xc03fffff以下的地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 和0x003fffff以下的地址都指向相同的页表，&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 这是为将地址映射为内核地址做准备&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; or eax, PG_US_U | PG_RW_W | PG_P&#9; ; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ;页目录项的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov [PAGE_DIR_TABLE_POS + 0x0], eax ; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ;第1个目录项,在页目录表中的第1个目录项写入第一个页表的位置(0x101000)及属性(7)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov [PAGE_DIR_TABLE_POS + 0xc00], eax ; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ;一个页表项占用4字节,0xc00表示第768个页表占用的目录项,0xc00以上的目录项用于内核空间,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&#9; ;也就是页表的0xc0000000~0xffffffff共计1G属于内核,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&#9; ;0x0~0xbfffffff共计3G属于用户进程.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sub eax, 0x1000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov [PAGE_DIR_TABLE_POS + 4092], eax&#9; ; 使最后一个目录项指向页目录表自己的地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>写一个操作系统_06 内存寻址</title><link>https://www.oneyearago.me/2019/03/26/make_a_linux_6/</link><pubDate>Tue, 26 Mar 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/03/26/make_a_linux_6/</guid><description>&lt;h4 id="保护模式"&gt;保护模式&lt;/h4&gt;&#10;&lt;h5 id="为什么要有保护模式"&gt;为什么要有保护模式&lt;/h5&gt;&#10;&lt;p&gt;  实模式是有很大弊端的，首先，直接操作物理内存，这样的话每次只能运行一个程序，并且不安全；另外，内存最大使用到1M，限制太大。&lt;/p&gt;&#10;&lt;p&gt;  保护模式下，程序不能直接访问物理地址，程序的虚拟地址需要被转换为物理地址后再去访问，地址转换是由处理器和操作系统协作完成的，处理器在硬件上提供地址转换部件，操作系统提供转换过程中需要的页表。&lt;/p&gt;&#10;&lt;h5 id="保护模式的寄存器扩展"&gt;保护模式的寄存器扩展&lt;/h5&gt;&#10;&lt;p&gt;除段寄存器外，通用寄存器，指令指针寄存器，标志寄存器等都从16位升到了32位，因为段寄存器保留原来的16位也够用。&lt;/p&gt;&#10;&lt;h5 id="模式转换"&gt;模式转换&lt;/h5&gt;&#10;&lt;p&gt;  bits伪指令用于指定处理器的运行模式，这一点是编译器和处理器约定的，操作数大小翻转前缀0x66和寻址方式反转前缀0x67用于将当前运行模式下的操作数大小和寻址方式转换成另一种模式。&lt;/p&gt;&#10;&lt;h4 id="内存寻址"&gt;内存寻址&lt;/h4&gt;&#10;&lt;h5 id="地址转换"&gt;地址转换&lt;/h5&gt;&#10;&lt;p&gt;  内存控制单元(MMU) 通过一种称为分段单元的硬件电路把一个逻辑地址转换成线性地址，接着，通过第二个称为分页单元的硬件电路把线性地址转换成物理地址。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;graph LR&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;逻辑地址--&amp;gt;线性地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;线性地址--&amp;gt;物理地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id="全局描述符表"&gt;全局描述符表&lt;/h5&gt;&#10;&lt;p&gt;  最初的操作系统是无法对内存段做访问限制，有了这样的需求以后，CPU厂商决定采用段描述符来实现相关的功能，在硬件一级上添加GDTR和LDTR来支持全局描述符表和局部描述符表，并由硬件负责周边的安全检测。当初的CPU厂商也并不是凭空制造出了这样一个概念，是与操作系统厂商共同协商后才有了一套硬件方面的支持。&lt;/p&gt;&#10;&lt;p&gt;  如今x86架构的OS，大都都把分段视为是兼容性的考虑（类似于对实模式的处理），在系统初始化阶段象征性地初始化GDT，之后的运行就没分段啥事儿了。地址空间的保护模型都来自分页，像ARM体系结构就不支持分段，仅靠MMU进行保护。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/linux_gdt.jpg" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="内存分页"&gt;内存分页&lt;/h4&gt;&#10;&lt;h5 id="为什么要分页"&gt;为什么要分页&lt;/h5&gt;&#10;&lt;p&gt;  分段的内存碎片太大，是计算中发展过程中尝试过的方案，现在的方案是内存分页，通过某种方式，将虚拟地址映射到物理地址，映射的关系是通过一张表实现的，也就是页表。&lt;br&gt;&#10;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/mem_page.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="分页机制"&gt;分页机制&lt;/h5&gt;&#10;&lt;p&gt;  分页机制的思想是:通过映射，可以使连续的线性地址与物理地址相关联，逻辑上连续的线性地址对应的物理地址可以不连续。&lt;br&gt;&#10;分页的作用&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;将线性地址转换为物理地址&lt;/li&gt;&#10;&lt;li&gt;用大小相同的页替换大小不同的段&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/page_addr.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="一级页表"&gt;一级页表&lt;/h5&gt;&#10;&lt;p&gt;  我们把一页的大小定义为4K,那么4G就有1M个页，在32位的保护模式下，地址都是32位二进制表示的，用20位二进制定位页表，剩余的12位表示4K里面的偏移。&lt;/p&gt;&#10;&lt;p&gt;  分页机制打开前要将页表地址加载到控制寄存器CR3中，这个过程是打开页表之前，所以存储的是物理实际地址，每个页表项对应一个物理页，通过页表项就可以访问到实际的物理地址。由于这个过程是固定的，CPU中集成了这个硬件模块，即MMU中的页部件。&lt;br&gt;&#10;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/page_table..png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="二级页表"&gt;二级页表&lt;/h5&gt;&#10;&lt;h6 id="为什么要二级页表"&gt;为什么要二级页表&lt;/h6&gt;&#10;&lt;p&gt;每个进程1M个页表，每个4字节，进程多了占用的内存还是很多的。&lt;/p&gt;&#10;&lt;p&gt;一般进程使用的内存是远低于全部虚拟内存的。二级模式只为进程实际使用的那些虚拟内存区分配页表，既提升了效率，也减少了内存的使用量。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/page-mechanism-0.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="页目录项结构"&gt;页目录项结构&lt;/h5&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/page-mechanism-1.gif" alt="image"&gt;&lt;/p&gt;&#10;&lt;h5 id="页表项结构"&gt;页表项结构&lt;/h5&gt;&#10;&lt;p&gt;&lt;img src="http://201904-1251969284.cossh.myqcloud.com/page-mechanism-2.gif" alt="image"&gt;&lt;/p&gt;&#10;&lt;h4 id="REF"&gt;REF&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;深入理解Linux内核&lt;/li&gt;&#10;&lt;li&gt;x86保护模式&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>写一个操作系统_05 调试MBR</title><link>https://www.oneyearago.me/2019/03/23/make_a_linux_5/</link><pubDate>Sat, 23 Mar 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/03/23/make_a_linux_5/</guid><description>&lt;h3 id="熟悉MBR引导"&gt;熟悉MBR引导&lt;/h3&gt;&#10;&lt;p&gt;MBR的512字节里面也是代码，他被BIOS加载执行，自身执行的时候加载操作系统的loader，我们在没有操作系统loader的情况下先写一段测试程序，体验一下。&lt;/p&gt;&#10;&lt;p&gt;实现的功能如下:&lt;/p&gt;&#10;&lt;p&gt;BIOS 加载 MBR中的代码，执行，用BIOS 中断 INT 0x10显示字符串&lt;/p&gt;&#10;&lt;h4 id="MBR的代码"&gt;MBR的代码&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;主引导程序 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 起始编译地址是 0x7c00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SECTION MBR vstart=0x7c00 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ax,cs &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ds,ax&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov es,ax&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ss,ax&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov fs,ax&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov sp,0x7c00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; 使用BIOS 显示服务（Video Service）--INT 10H &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;;;;;;;;; 下面这三行代码是获取光标位置 ;;;;;;;;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;.get_cursor获取当前光标位置,在光标位置处打印字符.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ah, 3&#9;&#9;; 输入: 3号子功能是获取光标位置,需要存入ah寄存器&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov bh, 0&#9;&#9;; bh寄存器存储的是待获取光标的页号&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int 0x10&#9;&#9;; 输出: ch=光标开始行,cl=光标结束行&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;; dh=光标所在行号,dl=光标所在列号&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;;;;;;;;; 获取光标位置结束 ;;;;;;;;;;;;;;;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;;;;;;;;; 打印字符串 ;;;;;;;;;;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ;还是用10h中断,不过这次是调用13号子功能打印字符串&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ax, message &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov bp, ax&#9;&#9;; es:bp 为串首地址, es此时同cs一致，&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;; 开头时已经为sreg初始化&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ; 光标位置要用到dx寄存器中内容,cx中的光标位置可忽略&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov cx, 8&#9;&#9;; cx 为串长度,不包括结束符0的字符个数&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov ax, 0x1301&#9;; 子功能号13是显示字符及属性,要存入ah寄存器,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;; al设置写字符方式 ah=01: 显示字符串,光标跟随移动&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mov bx, 0x2&#9;&#9;; bh存储要显示的页号,此处是第0页,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;; bl中是字符属性, 改颜色，要不看不见&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; int 0x10&#9;&#9;; 执行BIOS 0x10 号中断&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;;;;;;;;; 打字字符串结束&#9; ;;;;;;;;;;;;;;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jmp $&#9;&#9;; ; 死循环&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; message db &amp;#34;TEST MBR&amp;#34;&#9;; 字符串定义&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ; 保证 MBR 512B &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; times 510-($-$$) db 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; db 0x55,0xaa&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="编译成二进制"&gt;编译成二进制&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nasm -o mbr.bin mbr.S&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="将二进制文件存储到MBR扇区"&gt;将二进制文件存储到MBR扇区&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dd if=./mbr.bin of=./hd30M.img bs=512 count=1 conv=notrunc&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="调试-MBR-代码"&gt;调试 MBR 代码&lt;/h4&gt;&#10;&lt;p&gt;在 bochs的配置文件中配好 MBR扇区对应的磁盘，启动bochs&lt;/p&gt;</description></item><item><title>写一个操作系统_04 内核调试环境搭建</title><link>https://www.oneyearago.me/2019/03/20/make_a_linux_4/</link><pubDate>Wed, 20 Mar 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/03/20/make_a_linux_4/</guid><description>&lt;h3 id="环境准备工作"&gt;环境准备工作&lt;/h3&gt;&#10;&lt;p&gt;有一台win10的笔记本，调试想选择在Linux环境下，选择配置虚拟机，然后安装bochs调试内核&lt;br&gt;&#10;需要准备的软件:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;VirtualBox和增加工具包&lt;/li&gt;&#10;&lt;li&gt;安装CentOS 6.8minimal&lt;/li&gt;&#10;&lt;li&gt;设置CentOS的网卡，使之能上网&lt;/li&gt;&#10;&lt;li&gt;用yum安装开发工具和GNOME&lt;/li&gt;&#10;&lt;li&gt;安装bochs&lt;/li&gt;&#10;&lt;li&gt;进入GNOME，在GNOME中运行bochs&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="虚拟机"&gt;虚拟机&lt;/h4&gt;&#10;&lt;h5 id="CentOS配置"&gt;CentOS配置&lt;/h5&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;虚拟机设置中连接方式为NAT&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;因为安装的CentOS是minimal版本，所以网卡没有默认设置为启动&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vi /etc/sysconfig/network-scripts/ifcfg-eth0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;修改:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ONBOOT=&amp;#34;yes&amp;#34; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MM_Controlled=&amp;#34;no&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ifconfig eth0 192.168.0.2 netmask 255.255.255.0 //修改ip地址、子网掩码&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# route add default gw 192.168.0.1 dev eth0　　&#9; //修改网关 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# hostname centos 　　&#9;&#9;&#9;&#9;&#9; //修改计算机名 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# service network restart &#9;&#9;&#9;&#9; //重启网络&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ping 192.168.1.1&#9;&#9;&#9;&#9;&#9; //ping网关应正常&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# ping www.163.com&#9;&#9;&#9;&#9; //ping外网应正常&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;保证能上外网&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h5 id="需要的编译软件"&gt;需要的编译软件&lt;/h5&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# yum groupinstall &amp;#34;Development Tools&amp;#34;&#9;//gcc, gcc-c++等常用软件都会装上&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# yum -y install vim*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# yum install ncurses-devel zlib-devel texinfo gtk2-devel qt-devel&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# yum install tcl-devel tk-devel kernel-headers kernel-devel&#9;&#9;&#9;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# yum install gtk*&#9;&#9;&#9;//前一行的gtk2-dvel已经安装，此行可不执行&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# yum install build-essential nasm&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id="安装Gnome"&gt;安装Gnome&lt;/h5&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# yum grouplist ｜more &#9;&#9;&#9;&#9;&#9;//察看Gnome安装包的名称&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# yum groupinstall &amp;#34;X Window System&amp;#34;&#9;&#9;// 安装基本的X系统组件&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# yum groupinstall &amp;#34;Desktop &amp;#34;&#9;&#9;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# startx&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;//进入Gnome&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id="编译安装bochs"&gt;编译安装bochs&lt;/h5&gt;&#10;&lt;p&gt;我是下载了bochs-2.6.7.tar.gz的包通过虚拟机的共享目录转到CentOS下，然后在linux下操作&lt;/p&gt;</description></item><item><title>写一个操作系统_03 引导内核</title><link>https://www.oneyearago.me/2019/03/18/make_a_linux_3/</link><pubDate>Mon, 18 Mar 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/03/18/make_a_linux_3/</guid><description>&lt;h2 id="操作系统引导"&gt;操作系统引导&lt;/h2&gt;&#10;&lt;h3 id="MBR"&gt;MBR&lt;/h3&gt;&#10;&lt;p&gt;  接上一篇BIOS启动，BIOS完成了基础的硬件检测和硬件的中断向量表的初始化，然后BIOS找到MBR并且把MBR加载在内存中，跳转到该位置。加载的位置在内存中的0x7C00,至于为什么是这个位置，主要是因为历史的原因吧，最初的内存只有32K,历史选择了0x7C00(31k)。&lt;/p&gt;&#10;&lt;p&gt;  MBR引导扇区的内容是：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;446字节的引导程序及参数&lt;/li&gt;&#10;&lt;li&gt;64字节的分区表（每个分区表项16字节，因此只能有4个主分区）&lt;/li&gt;&#10;&lt;li&gt;2字节的结束标志0x55和0xaa&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;  MBR只是操作系统引导的其中一个环节，并不是最直接的引导代码，MBR的主要作用是加载操作系统提供的bootloader。MBR怎么知道loader存在哪个分区呢？分区表项的 第一个字节就是活动分区标志，如果该分区存储了加载器，该标志被置为0x80，否则是0.如果有多个活动分区，选择第一个活动分区引导。&lt;/p&gt;&#10;&lt;p&gt;  为了方便MBR找到内核加载器，约定好加载器就存储在各分区的开始扇区，这个扇区被称为操作系统引导扇区也称为OBR(OS Boot Record), 扇区里面的程序便是内核加载器，比如我们常见的x86平台上的grub或者arm平台上的uboot，这个程序负责的主要工作是加载kernel image 并解压缩。&lt;/p&gt;&#10;&lt;h3 id="BootLoader"&gt;BootLoader&lt;/h3&gt;&#10;&lt;p&gt;  Bootloader 主要完成了下面几项工作：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;bootloder 程序查找 Linux Kernel 镜像在启动盘中的位置（要么存放磁盘中的固定位置，要么通过文件系统去解析）。&lt;/li&gt;&#10;&lt;li&gt;将找到的 Linux Kernel 镜像和 initrd 文件加载到物理内存中的指定地址处，并建立运行内核的所需的基本环境。&lt;/li&gt;&#10;&lt;li&gt;最后，Bootloader 就将控制权转交到 Linux 内核，然后由内核开始执行。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;  GRUB 是我们现在 Linux 发行版系统中最常用到的 Bootloader，它的优势在于它可以识别 Linux 文件系统，例如 ext3，ext4 格式的文件系统。&lt;br&gt;&#10;GRUB 可以从 ext3 或者 ext4 格式文件系统的磁盘分区中加载 Linux Kernel 镜像。&lt;/p&gt;&#10;&lt;h3 id="Linux-Kernel-镜像"&gt;Linux Kernel 镜像&lt;/h3&gt;&#10;&lt;p&gt;  Linux 内核有多种格式的镜像，例如 vmlinux、Image、zImage、bzImage、uImage、xipImage、bootpImage 等。&lt;/p&gt;&#10;&lt;h4 id="vmlinux"&gt;vmlinux&lt;/h4&gt;&#10;&lt;p&gt;  vmlinux 是可引导的、未压缩、可压缩的内核镜像，vm 代表Virtual Memory，Linux 支持虚拟内存，因此得名 vm。它是由用户对内核源码编译得到，实质是 ELF 格式的文件，也就是说vmlinux 是编译出来的最原始的内核文件，未被压缩过。&lt;/p&gt;&#10;&lt;h4 id="zImage"&gt;zImage&lt;/h4&gt;&#10;&lt;p&gt;  zImage 是 ARM Linux 常用的一种压缩镜像文件，它是由vmlinux 加上解压代码经 gzip 压缩而成，命令格式是 make zImage，这种格式的 Linux 内核镜像文件多存放在 NAND Flash 上。&lt;/p&gt;</description></item><item><title>东西冲海岸线</title><link>https://www.oneyearago.me/2019/03/17/Coastline/</link><pubDate>Sun, 17 Mar 2019 23:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/03/17/Coastline/</guid><description>&lt;p&gt;去东西冲转，记录一下：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;穿专业点的徒步鞋，有的上下坡很滑，土很滑&lt;/li&gt;&#10;&lt;li&gt;花草扎人，长袖&lt;/li&gt;&#10;&lt;li&gt;很多地方上坡超过70度，注意安全，天气不好就改天吧&lt;/li&gt;&#10;&lt;li&gt;Enjoy :)&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/dxc1.jpg" alt="image"&gt;&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/dxc2.jpg" alt="image"&gt;&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/dxc3.jpg" alt="image"&gt;&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/dxc4.jpg" alt="image"&gt;&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/dxc5.jpg" alt="image"&gt;&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/dxc6.jpg" alt="image"&gt;&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/dxc7.jpg" alt="image"&gt;&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/dxc8.jpg" alt="image"&gt;&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/dxc9.jpg" alt="image"&gt;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>写一个操作系统_02 硬件与BIOS</title><link>https://www.oneyearago.me/2019/03/14/make_a_linux_2/</link><pubDate>Thu, 14 Mar 2019 21:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/03/14/make_a_linux_2/</guid><description>&lt;h2 id="硬件与BIOS流程"&gt;硬件与BIOS流程&lt;/h2&gt;&#10;&lt;h3 id="硬件"&gt;硬件&lt;/h3&gt;&#10;&lt;p&gt;   传统来说，主板上两个主要芯片，靠上方的叫北桥，靠下方的叫南桥。&lt;/p&gt;&#10;&lt;p&gt;   大体上说：北桥负责与CPU通信，并且连接高速设备（内存/显卡），并且与南桥通信；南桥负责与低速设备（硬盘/USB）通信，时钟/BIOS/系统管理/旧式设备控制，并且与北桥通信。&lt;/p&gt;&#10;&lt;p&gt;   Intel从第一代Core i7 (i7 9xx)开始，将原属于北桥功能的内存控制器整合到CPU当中，在主流机Core i中(i7 8xx)更将PCI-e控制器（主要负责连接显卡）整合到CPU当中，这时候传统意义上的北桥的所有功能都已经整合到CPU内部了，所以Intel 50系芯片“组”（X58除外，这是搭配i7 9xx用的，还有北桥）已经没有传统意义的北桥了，而南桥依然负责处理低速设备（SATA/USB/PCI等）、时钟等功能。&lt;/p&gt;&#10;&lt;h3 id="CPU的第一条指令"&gt;CPU的第一条指令&lt;/h3&gt;&#10;&lt;p&gt;   正常人开机都是等到操作系统加载完毕，进入操作系统然后开始使用应用软件，在高度抽象的角度使用CPU、内存、显卡等硬件。从CPU执行每一条指令的角度，这个过程又可以分为几十个子过程，并且经过了几十年的发展优化。&lt;/p&gt;&#10;&lt;p&gt;   我们来看一下CPU的第一条指令，CPU遵循着最简单的工作模式，从CS:IP 拿到内存的地址，取址执行，但是当主机刚刚上电，内存数据还没有初始化的时候，第一条指令从哪里来呢？CPU毕竟是硬件设备，也需要搭配硬件设备工作，有一类内存是掉电数据不丢失的，比如EEPROM(电可擦除可编程只读存储器),它被广泛用于需要经常擦除的BIOS芯片以及闪存芯片; DRAM:动态随机存取存储器,一般指物理内存….&lt;/p&gt;&#10;&lt;p&gt;  Intel的CPU具有很好的向后兼容性。在16位的8086 CPU时代，内存限制在1MB范围内，且BIOS的代码固化在EPROM中。在基于Intel的8086 CPU的PC机中的EPROM被编址在1ＭB内存地址空间的最高64KB中。PC加电后，CS寄存器初始化为0xF000，IP寄存器初始化为0xFFF0，所以CPU要执行的第一条指令的地址为CS:IP=0xF000:0XFFF0（Segment:Offset 表示）=0xFFFF0（Linear表示）。这个地址位于被固化EPROM中，指令是一个长跳转指令JMP F000:E05B。这样就开启了BIOS的执行过程。&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/neicun.png" alt="image"&gt;&lt;/p&gt;&#10;&lt;p&gt;这个过程有CPU硬编码的地方，比如上电后CS:IP的地址是0xFFFF0，BIOS的地址也是固化的，这个玩意就是标准，你没办法单独搞一个。&lt;/p&gt;&#10;&lt;h3 id="BIOS的工作"&gt;BIOS的工作&lt;/h3&gt;&#10;&lt;p&gt;  BIOS主要完成外设的检测以及中断向量表的初始化，外设的初始化信息和检测信息一般在自己的固件上，供BIOS调用。BIOS读外设固件的信息，在实模式内存的0x000 – 0x3FF 建立可操作硬件的中断向量表，每个中断向量占用4个字节，共可存储256个中断向量。这样就可以通过 ‘int 中断号’实现对应的硬件调用。&lt;/p&gt;&#10;&lt;p&gt;  历史在发展，科技在进步，目前的BIOS逐渐被UEFI取代，不过其基本作用是一样的，BIOS也更好理解。&lt;/p&gt;&#10;&lt;h3 id="完成历史使命"&gt;完成历史使命&lt;/h3&gt;&#10;&lt;p&gt;  BIOS做完计算机硬件自检和初始化后，会选择一个启动设备（例如软盘、硬盘、光盘等），并且读取该设备的第一扇区(即主引导扇区或启动扇区)到内存一个特定的地址0x7c00处，然后CPU控制权会转移到那个地址继续执行。至此BIOS的初始化工作做完了。&lt;/p&gt;&#10;&lt;p&gt;  CPU的控制权交到了 0x7C00, 如果我们把操作系统放到磁盘或光盘，接下来就可以加载操作系统到内存，执行我们的操作系统了。&lt;/p&gt;&#10;&lt;p&gt;  那是一片更广阔的天地。&lt;br&gt;&#10;&lt;img src="http://201903-1251969284.cosgz.myqcloud.com/Apple1-Mainboard.jpg" alt="image"&gt;&lt;br&gt;&#10;图为苹果计算机的第一代产品&lt;/p&gt;</description></item><item><title>男人与婚姻</title><link>https://www.oneyearago.me/2019/01/24/about_marriage/</link><pubDate>Thu, 24 Jan 2019 22:08:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/01/24/about_marriage/</guid><description>&lt;blockquote&gt;&#10;&lt;p&gt;有朋友问到区分激情和爱情，其实很好区分的，当你不见她时茶饭不思，是激情。老想见到她，想和她一起玩，一起上床，一起做白日梦，是激情。每天短信电话多的没完，是激情。为了生日节日费尽心思想浪漫的点子，是激情。当你和她在一起时脑子里不自觉的规划实实在在的将来，是爱情。当你们争吵到很凶，火很大时，也不忍心说一句伤害她的话，是爱情。当你们有分歧时，你总是能清楚的知道她是怎么想的，能理解她的初衷，是爱情。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;现在很多人谈恋爱很短时间就结婚了，当然，不是说时间短就不好。而是要清楚的知道，婚前的考察对这一生的影响。失败的家庭是任何成功都弥补不了的。纵然是家产万贯，顶一顶绿帽子也是没脸的事。再大的房子，妻离子散的日子也难过。人，一旦离过一次婚，再寻找幸福的难度就更大了。因为，离过婚的人更难相信爱情。更何况离婚对子女的影响，太大太大，而男人，一旦上了年纪，什么都没有子女重要了。人，真正能留在这个世界上的，也就是子女这点血脉而已。为子女营造一个好的成长环境，是父亲的责任。&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;今天晚上，得知我最好的朋友离婚了。孩子才1岁，同样身为父亲的我感到难过，离婚对孩子来说影响太大。所以想劝劝还没结婚的兄弟对待婚姻再慎重一些。本来，我结婚也没几年，没资格以过来人的身份说这些。不过，至少我的婚姻很幸福，我也有把握延续这种幸福。我开这个帖的目的，就是希望有更多的人能把握自己的婚姻。就算我说的都是废话，如果能引起朋友们在婚前对婚姻多一些思考，就达到目的了。&lt;/p&gt;&#10;&lt;p&gt;　　 每个人对婚姻的态度都是不同的，随便找个人凑合过日子的，找个女人帮自己生孩子的，找个过夜不收钱的，这些所谓的“婚姻”就不在讨论范围了，这里和大家讨论的，是高质量的婚姻。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 什么是高质量的婚姻？每个人的理解都不一样，在我看来，婚姻的状态有很多种，但是让人舒服的婚姻，一定都是有爱的。所以，我给还没结婚的兄弟第一个忠告：如果想拥有幸福的婚姻，一定要相信爱情。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 有人说，爱情是有保质期的。我说，有保质期的不叫爱情。那叫激情，激情夹杂的东西太多，性欲，感动，内疚，憧憬，有太多太多的杂质，这样的情感确实难以持久。何况，激情往往是精心呵护起来的，一旦丢失了精心呵护的动力，褪色太快。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 泡MM的时候，激情是最好的工具。然而面临结婚选择时，作为男人，一定要理清自己的头脑，祛除激情的成分。这个思考的过程非常重要，婚姻是没有回头路的。别以为大不了还可以离婚。离婚不是解脱，是又一个麻烦的开始。 　　 爱情是双方面的，先说说男人对女人的。和一个自己不爱的女人过一辈子。绝对是折磨。 　　 如何确定自己爱不爱一个女人，这是非常关键的一步。别以为这是废话，我很肯定的说，很少男人清楚这个问题。男人的生理天性决定了对女人的选择很大因素是外貌。但是婚姻是反人类天性的。所以当男人选择了婚姻，就注定要克服自己的生理天性。&lt;br&gt;&#10;　&lt;br&gt;&#10;男人的生理天性，说白就是希望保质保量的遗传自己的基因，比如尽可能的和更多女人上床，尽可能的选择更漂亮，基因更优秀的女人上床。而婚姻，恰恰相反，男人只能选择一个女人。而且要知道，眼前这个将要嫁给自己的美女，不几年就会生孩子，眼角会有皱纹，乳房会下垂，乳头会变的很黑，屁股会变形，小肚子会出&lt;br&gt;&#10;来，还会有难看的斑纹。更可怕的是，男人的生理天性决定了男人老和同一个女人上床，是会腻的。所以，没有爱的婚姻是危险的。因为没有克服生理欲望的信念。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 兄弟们永远别在婚前信誓旦旦的说自己不是那种包二奶的人。50年代出身那批人，道德观念比我们重的多，老婆还都是共过患难的糟糠，不一样大量的出轨？所以，在一夫一妻的制度下，保障婚姻的根本，还是得依靠爱情。其他约束在人性本能的拉扯下都是脆弱的。&lt;/p&gt;&#10;&lt;p&gt;据我观察，很多失败婚姻的元凶就是性！性对男人的诱惑是致命的，不少男人就是因为和女人上了床就顺理成章的结婚了。是好是坏全看运气。压根不思考有没有共同语言，遇到问题双方的沟通方式彼此能否接受，这些关键的问题不去想。就因为上了床就结婚。这是很多悲剧的来源。用人性本能来适应反人性本能的婚姻制度。这是非常愚蠢的。更愚蠢的是很多女人以性为工具来对待婚姻。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 说到这里才发现，婚姻真TMD是个大话题。每个人去领结婚证的时候，都没想过有一天会去拿离婚证。可惜，离婚的人越来越多。据说80后的离婚率快到百分四十了。真奇怪，怎么中国没有做婚姻辅导的。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 接着说吧，男人，只有把性欲，感动等等等等因素全抛开，才能真正认识自己对一个女人的感情是不是爱。爱不能解决所有问题，但是能给男人解决问题的动力。没有动力经营的婚姻，麻烦很多。因为婚姻的问题很多。&lt;br&gt;&#10;　&lt;br&gt;&#10;千万别把结婚看成是好事。结婚对男人而言，纯粹是责任。有了承担责任的决心，才敢谈婚姻，所以，结婚一定要找一个自己真爱的女人，凑合不得。自己余下的生命全部为之付出的家庭，女主人不是自己真正爱的，太悲剧了。因为婚姻唯一能回报给男人的，仅仅是一只能牵着自己走向死亡的手。千万别奢望婚姻回报给自己什么，任何奢望只会添加双方的压力。做好丈夫该做的，自然得到应得的。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　选择结婚对象，我觉得有几点一定要仔细思考：　&lt;br&gt;&#10;　&lt;br&gt;&#10;　 1。双方沟通方式彼此能否接受。&lt;br&gt;&#10;这是最重要的一点，不是平时有没有共同语言，而是有分歧和问题的沟通。说白了，如果男人是个讲道理的人，女人也得是个懂道理的人，如果男人是个喜欢用拳头说话的人，女人也得是个肯挨拳头的人。女人如果喜欢唠叨，男人就得听得惯。总之，两个人必须得有个拿出统一意见的程序，且双方都能接受。最好是都乐意接&lt;br&gt;&#10;受。其实这一条对于真正相爱的人来讲不是问题，真正相爱的两个人永远是站在对方立场思考问题的。&lt;/p&gt;&#10;&lt;p&gt;　　 夫妻意见的统一，是家庭和睦的基石。&lt;/p&gt;&#10;&lt;p&gt;　　 2。两人结合带来的压力是否可控。&lt;/p&gt;&#10;&lt;p&gt;有情饮水饱，可惜只饱一顿。男人没结婚，是潇洒的，一结婚，负担就来了。两个人的结合所带的压力肯定是巨大的，必须要考虑双方的承受能力，自己没有能力负担，就不要害人害己了。女朋友如果喜欢钱，有钱就娶，没钱就不要砸锅卖铁娶回家了。有些负担，背上就是一辈子的。对婚姻而言，肯陪自己一起奋斗的女人，才真正应该珍惜。单单是两个人一起改善生活的过程，就已经是婚姻宝贵的财富了。别说现在这样的女人少，不少。老盯着花枝招展想靠嫁人改变命运的女人，就别怪女人现实。自己从未规划过未来，就别怪女人不肯陪自己一起奋斗。&lt;br&gt;&#10;　　&lt;br&gt;&#10;我为什么一直提倡婚前性行为？性生活得到满足的男人，才更容易发现女人其他的魅力。而现实的情况是，不少好女人还剩着，男人却在追逐性的过程中迷失了自己。因色而结合，出了问题凑合，女人色衰之后，没本事的男人继续凑合，有本事的换个女人凑合。这样的风气还有个很烦的影响，女人越来越在意自己的形象，化妆时间越来越长，衣领越来越低，裙子越来越短。而充电的时间越来越少，独立意识越来越淡。男人一边追逐女色，一边抱怨女人素质越来越低。女人呢？一边抱怨男人肤浅，一边不断迎合肤浅的审美。&lt;/p&gt;&#10;&lt;p&gt;3。双方是否是对方的助力。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 婚姻不是人生的全部，两个人携手一生，相助则利，相阻则损。性格互补也好，志同道合也好，彼此成为对方的助力，真的很重要。如果和一个女人婚前就感觉疲惫，相信我，别接了。家是港湾，是一个让男人快到家门就会不自觉加快脚步的地方。是一个一回家就会彻底放松，卸下所有伪装的地方。是一个所有笑容都发自真心的地方。&lt;br&gt;&#10;　　 结婚，一定娶一个让自己感到轻松的女人。真的！&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 说了这么多，肯定有兄弟说这么多条件的女人难找。其实真的不难，第一条和第三条，只要女人爱你都不是问题。第二条，只要在和自己同一层次的女人中间找也不是问题。&lt;br&gt;&#10;　　&lt;br&gt;&#10;有朋友问到区分激情和爱情，其实很好区分的，当你不见她时茶饭不思，是激情。老想见到她，想和她一起玩，一起上床，一起做白日梦，是激情。每天短信电话多的没完，是激情。为了生日节日费尽心思想浪漫的点子，是激情。当你和她在一起时脑子里不自觉的规划实实在在的将来，是爱情。当你们争吵到很凶，火很大时，也不忍心说一句伤害她的话，是爱情。当你们有分歧时，你总是能清楚的知道她是怎么想的，能理解她的初衷，是爱情。&lt;/p&gt;&#10;&lt;p&gt;现在很多人谈恋爱很短时间就结婚了，当然，不是说时间短就不好。而是要清楚的知道，婚前的考察对这一生的影响。失败的家庭是任何成功都弥补不了的。纵然是家产万贯，顶一顶绿帽子也是没脸的事。再大的房子，妻离子散的日子也难过。人，一旦离过一次婚，再寻找幸福的难度就更大了。因为，离过婚的人更难相信爱情。更何况离婚对子女的影响，太大太大，而男人，一旦上了年纪，什么都没有子女重要了。人，真正能留在这个世界上的，也就是子女这点血脉而已。为子女营造一个好的成长环境，是父亲的责任。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 前面说这么多其实都是说面对婚姻的态度要理性，要慎重。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　 态度端正以后，技巧也有很多需要注意的地方。明明相爱的两个人互相伤害的事情太多了。伤寒心散伙的也多，而且婚姻生活，男人还有个重要技术要掌握，就是老妈与老婆的关系。&lt;/p&gt;&#10;&lt;p&gt;婚后生活琐碎的事情太多，先说说处理婆媳关系。所有还没结婚的兄弟一定不要把这不当一回事。一边是生你养你为你付出所有的母亲，一边是将要相伴你一生的老婆。双方都有责任要照顾。这个关系弄顺了，少很多麻烦。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　 有人说男人在婆媳之间是双面胶，夹在中间两面讨好。这是错的。男人必须是一手拿棒一手拿润滑油。小事居中调节，原则问题对事不对人，道理绝不能歪，既不能纵容媳妇，也不能惯坏了老妈。坚决不要在老妈面前说媳妇不对，多包涵之类，也不能在媳妇面前说，这都是纵容。把握一个原则，绝不允许媳妇在自己面前说老妈的坏话，有事说事。该怎么解决怎么解决，是老妈不对也要和老妈把事情讲清楚，同样的，老妈说自己媳妇的时候也要分清是非。居中讨好的后果是两面不是人，矛盾还越来越深。&lt;br&gt;&#10;　　&lt;br&gt;&#10;总之，绝对的公平是处理婆媳关系的唯一方式，委屈任何一方，都会使矛盾激化。妈也好，老婆也好，底线都给她们画好。人都是有选择性的，既然要不到特权，自然会注意彼此相处。一开始可能男人日子不太好过，两边都要斗上几次。但是慢慢的，家庭秩序就会走上正轨。 如果一开始图省心，哄过去，两边脾气都养大了就够自己受了。（前面忘了说了，找女人一定要找个聪明的，笨女人多很多麻烦，聪明的女人自己会处理，自己只用打打下手，关系就处的很好了。）&lt;br&gt;&#10;　　&lt;br&gt;&#10;还有个问题，就是子女，现在80后生的孩子才真叫一个宝贝，一大家人围着转，各有各的主意，很多矛盾都是因此而起，在这一点上，男人一定要强硬，从一开始就绝对不能让步，要给出明确的信号，孩子是自己的。双方父母肯帮忙，感谢。但是涉及孩子的一切决定。必须是自己拿主意。（这一点背地里可以多听老婆的，毕&lt;br&gt;&#10;竟，妈妈是最爱孩子的。）这一点申明尤其重要，相当于自己一个人把所有炮火揽了。否则，孩子一点点小感冒家里可以闹翻天。（我那今天离婚的朋友就是因为这个）。&lt;/p&gt;&#10;&lt;p&gt;婚姻确实是个大话题，一时反而不知道说什么好了，我是真心想和大家交流一下。因为我自认为自己的婚姻真的是种幸福。我是真心希望有更多的人能愉快的享受婚姻。有人说不吵架不算夫妻，说真的，我和我老婆真还吵不起来架，就像我前面说的，伤彼此的话确实不忍心讲出口。何况一旦清楚彼此都是出于爱，又有什么好吵的？有几次刚进入状态，看见彼此装腔作势生气的样子就都笑了。&lt;br&gt;&#10;　　&lt;br&gt;&#10;　　　&lt;br&gt;&#10;　　 恋人时刻只有甜蜜和浪漫，而婚姻则更多是责任和平淡，这个转化的过程，是需要双方有充足思想准备的。其实只要两个人肯一起面对，婚姻生活平淡中的幸福并不输给热恋时的浪漫。&lt;/p&gt;&#10;&lt;p&gt;愿意花时间看这帖子的兄弟姐妹们，光是这一点就足以说明你们对待婚姻的态度是充满诚意的。有这样的态度你们一定会收获属于你们的幸福。真心话。&lt;/p&gt;&#10;&lt;h4 id="REF"&gt;REF&lt;/h4&gt;&#10;&lt;p&gt;引用: &lt;a href="https://www.douban.com/note/145400834/"&gt;https://www.douban.com/note/145400834/&lt;/a&gt;&lt;/p&gt;</description></item><item><title>vmstat</title><link>https://www.oneyearago.me/2019/01/04/vmstat/</link><pubDate>Fri, 04 Jan 2019 12:31:45 +0800</pubDate><guid>https://www.oneyearago.me/2019/01/04/vmstat/</guid><description>&lt;p&gt;最近遇到了机器特别卡，请求发到服务器收不到响应的情况，总结了下 vmstat 定位系统性能问题的用法&lt;/p&gt;&#10;&lt;h4 id="快速上手"&gt;快速上手&lt;/h4&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[root@VM_80_251_centos ~]# vmstat 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; r b swpd free buff cache si so bi bo in cs us sy id wa st&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 4 0 0 86704 283040 1354508 0 0 1 16 5 0 0 0 99 0 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0 0 0 86712 283040 1354508 0 0 0 12 231 327 1 0 99 0 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0 0 0 86712 283040 1354508 0 0 0 0 193 265 0 0 100 0 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0 0 0 86712 283040 1354512 0 0 0 0 173 235 0 1 99 0 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;r : 运行队列进程个数&lt;/li&gt;&#10;&lt;li&gt;si: 每秒交换分区写入内存量&lt;/li&gt;&#10;&lt;li&gt;so: 每秒内存写交换分区的量&lt;/li&gt;&#10;&lt;li&gt;bi: 每秒从块设备读取的块数量&lt;/li&gt;&#10;&lt;li&gt;bo: 每秒写入块设备的块数量&lt;/li&gt;&#10;&lt;li&gt;id: cpu 空闲状态百分比&lt;/li&gt;&#10;&lt;li&gt;wa: 等待io 所消耗的cpu 时间&lt;/li&gt;&#10;&lt;li&gt;st: 用于虚拟机，如果你的机器是虚拟化设备，虚拟 CPU 等待实际 CPU 的时间的百分比&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这些指标我们在 top 里面也是可以看到的&lt;/p&gt;</description></item><item><title>关于</title><link>https://www.oneyearago.me/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://www.oneyearago.me/about/</guid><description>&lt;p&gt;思绪来的快去的也快，偶尔在这里停留。&lt;/p&gt;</description></item></channel></rss>