-
Notifications
You must be signed in to change notification settings - Fork 0
/
atom.xml
475 lines (223 loc) · 472 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>boolsatellite</title>
<link href="http://example.com/atom.xml" rel="self"/>
<link href="http://example.com/"/>
<updated>2024-07-20T14:53:04.790Z</updated>
<id>http://example.com/</id>
<author>
<name>boolsatellite</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>决胜网络协议</title>
<link href="http://example.com/2024/07/20/%E5%86%B3%E8%83%9C%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/"/>
<id>http://example.com/2024/07/20/%E5%86%B3%E8%83%9C%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/</id>
<published>2024-07-19T16:00:00.000Z</published>
<updated>2024-07-20T14:53:04.790Z</updated>
<content type="html"><![CDATA[<h3 id="序列号回绕"><a href="#序列号回绕" class="headerlink" title="序列号回绕"></a>序列号回绕</h3><p>我们这里假设只有 8位 用来描述<code> tcp</code> 的序列号</p><p>seq1 = 255 seq2 = 1</p><p>使用减法来判断包的先后顺序 ,</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">bool</span> <span class="title function_">before</span><span class="params">(__u32 seq1, __u32 seq2)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">return</span> (__s32)(seq1-seq2) < <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> after(seq2, seq1) before(seq1, seq2)</span></span><br></pre></td></tr></table></figure><p>这里就变为了 before(0b1111 , 0b1) -> return 0b1110 < 0 , 采用了无符号数减法转化为有符号数来粗略的判断,当seq1 和 seq2之间的间距 大于 0xc000 时就无法判断了,就需要使用额外的东西来判断如时间戳</p><h3 id="窗口缩放"><a href="#窗口缩放" class="headerlink" title="窗口缩放"></a>窗口缩放</h3><p>tcp头部中指定了窗口大小,使用了16位,这意味着最大窗口位 64k,由于现代网络传输速度的加快,64k有时不能满足要求,于是在这上面打了补丁也就有了窗口缩放,用窗口大小乘缩放因子得到时机的窗口大小,缩放因子是在三次握手中协商的,如下报文</p><p><img src="/img/Snipaste_2024-07-10_20-12-53.png"></p><p>这里的缩放因子是 256 也就是实际的窗口大小为 516 * 256</p><h4 id="tcp的重要选项"><a href="#tcp的重要选项" class="headerlink" title="tcp的重要选项"></a>tcp的重要选项</h4><p> 在 <code>tcp</code>头部中选项和填充属于可选字段,但也有相当重要的</p><p>MSS: 最大段大小选项 , TCP 允许的从对方接收的最大报文段<br>SACK: 选择确定选项<br>Window Scale: 窗口缩放选项</p><h3 id="临时端口的分配"><a href="#临时端口的分配" class="headerlink" title="临时端口的分配"></a>临时端口的分配</h3><p> 在没有调用 bind 或者 bind 指定的端口号为 0 的时候会采用临时端口号</p><p>tcp协议栈用三个全局的 <code>inet_hash</code> 哈希表</p><ul><li>ehash: 负责有名的 socket,也就是四元组明确的 socket , key 是源地址 源端口 目的地址 目的端口组成的 , value 是对应的socket</li><li>bhash: 负责端口分配,key是端口号,value 是使用此端口的所有socket,一个socket 可同时在bhash 和 ehash 中使用</li><li>listening_hash: 负责 listen socket</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_hashinfo</span> {</span></span><br><span class="line"><span class="comment">/* This is for sockets with full identity only. Sockets here will</span></span><br><span class="line"><span class="comment"> * always be without wildcards and will have the following invariant:</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_ehash_bucket</span>*<span class="title">ehash</span>;</span></span><br><span class="line"><span class="type">spinlock_t</span>*ehash_locks;</span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span>ehash_mask;</span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span>ehash_locks_mask;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Ok, let's try this, I give up, we do need a local binding</span></span><br><span class="line"><span class="comment"> * TCP hash as well as the others for fast bind/connect.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">kmem_cache</span>*<span class="title">bind_bucket_cachep</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_bind_hashbucket</span>*<span class="title">bhash</span>;</span></span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span>bhash_size;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* The 2nd listener table hashed by local port and address */</span></span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span>lhash2_mask;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_listen_hashbucket</span>*<span class="title">lhash2</span>;</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p><code>inet_ehash_bucket</code> 说是哈希表,实际就是一个数组,数组的每一个元素都是一个 <code>inet_bind_hashbuchet</code> 指针,chain 字段是一个链表</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_ehash_bucket</span> {</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_nulls_head</span> <span class="title">chain</span>;</span></span><br><span class="line">};</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_bind_hashbucket</span> {</span></span><br><span class="line"><span class="type">spinlock_t</span>lock;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_head</span><span class="title">chain</span>;</span></span><br><span class="line">};</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">inet_listen_hashbucket</span> {</span></span><br><span class="line"><span class="type">spinlock_t</span>lock;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">hlist_nulls_head</span><span class="title">nulls_head</span>;</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>bind(0) 分配端口:</p><p><img src="/img/Snipaste_2024-07-11_11-50-02.png"></p><p><img src="/img/Snipaste_2024-07-11_14-06-37.png"></p><p>connect 分配端口:</p><p>首先要分配端口,与 bind(0) 不同的是 <code>offset &= ~1</code>,将offset 强制变为偶数之后与low相加检测bhash中是否有相同的 ,之后判断端口是否可用</p><p>对应 bind 端口为 0 时 ,所分配的临时端口是奇数,对应没有 bind 直接 connect ,所分配的端口是偶数</p><blockquote><p>高版本内核临时端口分配策略</p><p>/proc/sys/net/ipv4/ip_local_port_range 文件指定了临时端口号的下界 low 和 上界 high,默认情况下 low 是偶数</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">root@bool:~# cat /proc/sys/net/ipv4/ip_local_port_range </span><br><span class="line">3276860999</span><br></pre></td></tr></table></figure><ul><li>优先给 bind(0) 分配随机的与low奇偶性不同的端口,也就是奇数,如果奇数端口号分配完了才需要尝试分配偶数端口</li><li>优先给 connect 分配随机的与low奇偶性相同的端口,也就是偶数</li></ul></blockquote><p>协议栈端口选择导致的性能衰减:当客户端疯狂 connect 时,1w并发连接和2w并发连接的耗时完全无线性关系,而是呈现一种近指数上升的趋势。例如,1w并发链接建连1w次,耗时不到1s,如果改为2w并发链接,建连2w次,耗时突然变成了10+s。当我们完全占用偶数组的端口后,所有后续的<code>connect</code>调用,所需的源端口应该位于奇数组中,然而该函数依旧会尝试完整遍历偶数组资源,这也就是<code>__inet_check_established</code>耗时占比这么高的原因。简而言之,就是自kernel 4.2开始,端口资源的分配策略改了,目前奇数端口留给<code>bind</code>,偶数端口留给<code>connect</code>为了均衡资源的占用,但是显然,这种策略不适合本文所述的特殊场景,并且对于<code>bind</code>而言,也存在性能衰减的问题。</p><p>为什么 SYN / FIN段不携带数据却要消耗一个序列号?</p><ul><li>不占用序列号的段是不需要确认的,比如纯 ACK 包</li><li>SYN 段需要双方确认,需要占用一个序列号,若不进行确认,对端将无法辨别所发出的 SYN 是否已经被收到</li><li>凡是消耗序列号的 TCP 报文段,一定需要对端确认。如果这个段没有收到确认,会一直重传直到达到指定的次数为至</li></ul><h3 id="三次握手冷知识"><a href="#三次握手冷知识" class="headerlink" title="三次握手冷知识"></a>三次握手冷知识</h3><p>通信双方都知道对端开发的端口,同时调用 connect , 发送 syn 包,双方进入 SYN_SEND 状态,接收到对端传来的 SYN 时进入 SYN_REVD , 发送 SYN ACK , 进入 ESTABLISH</p><p><img src="/img/Snipaste_2024-07-12_14-06-24.png"></p><p>TCP 自连接:</p><p>当我们执行这段脚本:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">while ture</span><br><span class="line">do </span><br><span class="line">telnet 127.0.0.1 50000</span><br><span class="line">done</span><br></pre></td></tr></table></figure><p>过一段时间会出现</p><p><img src="/img/Snipaste_2024-07-12_14-19-31.png"></p><p>原理:</p><p><img src="/img/Snipaste_2024-07-12_14-23-25.png"></p><p>自连接的危害:</p><p>存在业务系统 B会访问本机服务 A , 服务A监听了 50000 端口</p><ol><li>业务系统B代码中增加了对服务A断开重连的逻辑</li><li>如果有一个服务A挂掉长时间没有启动,业务系统B开始不断的 connect</li><li>系统B经过一段时间的重试就会出现自连接的情况</li><li>这是服务A想要监听50000断开就会出现断开被占用的情况</li></ol><p>自连接的进程占用了端口,导致真正需要监听端口的服务程序无法正常启动,自连接的进程开启了connect成功了,实际上服务是不正常的,无法正常进行数据通信</p><h3 id="四次挥手中的同时关闭"><a href="#四次挥手中的同时关闭" class="headerlink" title="四次挥手中的同时关闭"></a>四次挥手中的同时关闭</h3><p><img src="/img/Snipaste_2024-07-13_00-01-43.png"></p><h3 id="两个队列"><a href="#两个队列" class="headerlink" title="两个队列"></a>两个队列</h3><p>半连接队列(Incomplete connection queue), 又称 SYN 队列<br>全连接队列(Completed connection queue), 又称 Accept 队列</p><p>当客户端发起 SYN 到服务端,服务端收到以后会回 ACK 和⾃⼰的 SYN。这时服务端这边的 TCP 从 listen 状态变为 SYN_RCVD (SYN Received),此时会将这个连接信息放⼊半连接队列</p><p><code>int listen(int fd , int backlog)</code></p><p>TCP套接字上的backlog参数的行为在Linux 2.2中发生了变化。现在,它指定等待接受的完全建立的套接字的队列长度,而不是不完整连接请求的数量。不完整套接字队列的最大长度可以使用 <code>/proc/sys/net/ipv4/tcp_max_syn_backlog</code> 设置。当启用同步cookie时,没有逻辑上的最大长度,此设置将被忽略。</p><p>如果backlog参数大于 <code>/proc/sys/net/core/somaxconn</code> 中的值,那么它将被静默地截断为该值。自Linux 5.4以来,此文件中的默认值为4096</p><p>半连接队列的大小和传入的 backlog , 系统参数 <code>somaxconn</code> ,<code>max_syn_backlog</code> 都有关系,通过其中最小值用于计算,盲目的调大backlog 对最终半连接队列的大小可能不会有影响</p><p>全连接队列包含了服务端所有完成了三次握⼿,但是还未被应⽤取⾛的连接队列。此时的 socket 处于 ESTABLISHED 状态。每次应⽤调⽤ accept() 函数会移除队列头的连接。如果队列为空, accept() 通常会阻塞。全连接队列也被称为 Accept 队列。</p><p>半连接队列满了以后会忽略 SYN<br>全连接队列满了之后会忽略客户端的 ACK,随后重传 SYN + ACK,可以通过 <code>/proc/sys/net/ipv4/tcp_abort_on_overflow</code> 决定</p><ul><li><code>tcp_abort_on_overflow</code> 为 0 表示三次握⼿最后⼀步全连接队列满以后 server 会丢掉 client 发过来的 ACK,服务端随后会进⾏重传 SYN+ACK。</li><li><code>tcp_abort_on_overflow</code> 为 1 表示全连接队列满以后服务端直接 发送 RST 给客户端。</li></ul><p>但是回给客户端 RST 包会带来另外⼀个问题,客户端不知道服务端 响应的 RST 包到底是因为「该端⼝没有进程监听」,还是「该端⼝ 有进程监听,只是它的队列满了」。</p><h3 id="TCP的定时器"><a href="#TCP的定时器" class="headerlink" title="TCP的定时器"></a>TCP的定时器</h3><p><code>tcp</code> 为每条连接建立了 7 个定时器:连接建立定时器,重传定时器,延迟ACK定时器,PERSIST定时器,KEEPALIVE定时器,FINWAIT2定时器,TIME_WAIT定时器 </p><p>Persist 定时器是专⻔为零窗⼝探测⽽准备的。我们都知道 TCP 利⽤滑动窗⼝来实现流量控制,当接收端 B 接收窗⼝为 0 时,发送端 A 此时不 能再发送数据,发送端此时开启 Persist 定时器,超时后发送⼀个特殊的报⽂给接收端看对⽅窗⼝是否已经恢复,这个特殊的报⽂只有⼀个字节。</p><p>四次挥⼿过程中,主动关闭的⼀⽅收到 ACK 以后从 FINWAIT1 进⼊ FINWAIT2 状态等待对端的 FIN 包的到来,FINWAIT2 定时器的作 ⽤是防⽌对⽅⼀直不发送 FIN 包,防⽌⾃⼰⼀直傻等。这个值由 <code>/proc/sys/net/ipv4/tcp_fin_timeout</code> 决定,默认值为 60s</p><p>TIMEWAIT 定时器也称为 2MSL 定时器,可主动关闭连接的⼀⽅在 TIMEWAIT 持续 2 个 MSL 的时间,超时后端⼝号可被安全的重⽤。</p><h3 id="TCP-拥塞控制"><a href="#TCP-拥塞控制" class="headerlink" title="TCP 拥塞控制"></a>TCP 拥塞控制</h3><p>tcp 拥塞控制四大阶段:慢启动 , 拥塞避免 , 快速重传 , 快速恢复</p><p>在连接建⽴之初,你不知道对端有多快,如果有⾜够的带宽,你可以选择⽤最快的速度传输数据,但是如果 是⼀个缓慢的移动⽹络呢?如果发送的数据过多,只是造成更⼤的⽹络延迟。这是基于整 个考虑,每个 TCP 连接都有⼀个拥塞窗⼜的限制,最初这个值很⼩,随着时间的推移, 每次发送的数据量如果在不丢包的情况下,慢慢的递增,这种机制被称为「慢启动」</p><blockquote><p> 第⼀步,三次握⼿以后,双⽅通过 ACK 告诉了对⽅ ⾃⼰的接收窗⼜(rwnd)的⼤⼩,之后就可以互相发 数据了</p><p>第⼆步,通信双⽅各⾃初始化⾃⼰的「拥塞窗⼜」 (Congestion Window,cwnd)⼤⼩</p><p>第三步,cwnd 初始值较⼩时,每收到⼀个 ACK, cwnd + 1,每经过⼀个 RTT,cwnd 变为之前的两 倍。</p></blockquote><p><img src="/img/Snipaste_2024-07-20_14-58-22.png"></p><p>慢启动拥塞窗口(cwnd)肯定不能⽆⽌境的指数级增长下去,否则拥塞控制就变成了「拥塞失控」了,它的阈值称为「慢启动阈值」(Slow Start Threshold,ssthresh)。<code>ssthresh</code> 就是⼀道刹车,让拥塞窗⼜别涨那么快</p><ul><li>当 cwnd < ssthresh 时,拥塞窗⼜按指数级增长(慢启动)</li><li>当 cwnd > ssthresh 时,拥塞窗⼜按线性增长(拥塞避免),在这个阶段,每⼀ 个往返 RTT,拥塞窗⼜⼤约增加 1 个 MSS ⼤⼩,直到检测到拥塞为⽌</li></ul><p>当收到三次重复 ACK 时,进⼊快速恢复阶段。解释为⽹络轻度拥塞。</p><ul><li>拥塞阈值 ssthresh 降低为 cwnd 的⼀半:ssthresh = cwnd / 2</li><li>拥塞窗⼜ cwnd 设置为 ssthresh</li><li>拥塞窗⼜线性增加</li></ul>]]></content>
<summary type="html"><h3 id="序列号回绕"><a href="#序列号回绕" class="headerlink" title="序列号回绕"></a>序列号回绕</h3><p>我们这里假设只有 8位 用来描述<code> tcp</code> 的序列号</p>
<p>seq1 &#x3D; </summary>
<category term="网络编程" scheme="http://example.com/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/"/>
<category term="read" scheme="http://example.com/tags/read/"/>
</entry>
<entry>
<title>CAS 与 无锁队列</title>
<link href="http://example.com/2024/07/02/10.1%20cas/"/>
<id>http://example.com/2024/07/02/10.1%20cas/</id>
<published>2024-07-01T16:00:00.000Z</published>
<updated>2024-07-02T08:50:21.739Z</updated>
<content type="html"><![CDATA[<h1 id="CAS-与-无锁队列"><a href="#CAS-与-无锁队列" class="headerlink" title="CAS 与 无锁队列"></a>CAS 与 无锁队列</h1><p>假设我们有一全局变量 idx , 在执行 idx++ 时编译器会翻译为三条汇编指令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mov [idx] , %eax</span><br><span class="line">inc %eax</span><br><span class="line">mov %eax , [idx]</span><br></pre></td></tr></table></figure><p>当三条汇编语句紧挨着执行时,idx++ 是保证正确的,但是当存在多个线程时 idx++ 的正确性就有待考证了如:</p><table><thead><tr><th>线程一</th><th>线程二</th></tr></thead><tbody><tr><td>mov [idx] , %eax</td><td></td></tr><tr><td>inc %eax</td><td></td></tr><tr><td></td><td>mov [idx] , %eax</td></tr><tr><td></td><td>inc %eax</td></tr><tr><td></td><td>mov %eax , [idx]</td></tr><tr><td>mov %eax , [idx]</td><td></td></tr></tbody></table><p>当线程一和线程二按照这样的方式执行时,虽然在各自的线程中都执行了 idx++ 但是 idx 的结果只增加了一次 , 解决的方法可以是为临界区代码段添加互斥锁或自旋锁,对于互斥锁:当资源已被加锁时将会切换线程而自旋锁不会,自旋锁将在此线程上一直等待直到资源可用为止,二者的选择需要从资源的竞争程度与线程切换的开销方面考虑,这并不是这篇文章的重点,我们要写一个大多数人未知的东西(至少我是这样的)<strong>原子操作</strong></p><h2 id="汇编实现原子操作:"><a href="#汇编实现原子操作:" class="headerlink" title="汇编实现原子操作:"></a>汇编实现原子操作:</h2><p>“最轻量级的锁”,通常也叫”原子操作”,之所以加引号是因为他们在汇编级别并不是原子操作,是用多条指令完成的,这些操作大多都是利用CPU支持的汇编指令</p><p>最常见的原子操作有Compare and Exchange,Self Increase/Decrease等等</p><p><strong>80486 CPU 相关指令:</strong></p><blockquote><p>LOCK:这是一个指令前缀,在所对应的指令操作期间使此指令的<strong>目标操作数指定的存储区域锁定</strong>,以得到保护。</p><p>XADD:先交换两个操作数的值,再进行算术加法操作。多处理器安全,在80486及以上CPU中支持。</p><p>CMPXCHG:比较交换指令,第一操作数先和AL/AX/EAX比较,如果相等ZF置1,第二操作数赋给第一操作数,否则ZF清0,第一操作数赋给AL/AX/EAX。多处理器安全,在80486及以上CPU中支持。</p><p>XCHG:交换两个操作数,其中至少有一个是寄存器寻址.其他寄存器和标志位不受影响.</p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">Dump of assembler code for function _Z3incPii:</span></span><br><span class="line"><span class="comment"> 0x0000555555555229 <+0>: endbr64 </span></span><br><span class="line"><span class="comment"> 0x000055555555522d <+4>: push %rbp</span></span><br><span class="line"><span class="comment"> 0x000055555555522e <+5>: mov %rsp,%rbp</span></span><br><span class="line"><span class="comment"> 0x0000555555555231 <+8>: mov %rdi,-0x18(%rbp)</span></span><br><span class="line"><span class="comment"> 0x0000555555555235 <+12>: mov %esi,-0x1c(%rbp)</span></span><br><span class="line"><span class="comment"> 0x0000555555555238 <+15>: mov -0x18(%rbp),%rdx</span></span><br><span class="line"><span class="comment"> 0x000055555555523c <+19>: mov -0x1c(%rbp),%eax</span></span><br><span class="line"><span class="comment"> 0x000055555555523f <+22>: lock xadd %eax,(%rdx)</span></span><br><span class="line"><span class="comment"> 0x0000555555555243 <+26>: mov %eax,-0x4(%rbp)</span></span><br><span class="line"><span class="comment"> 0x0000555555555246 <+29>: mov -0x4(%rbp),%eax</span></span><br><span class="line"><span class="comment"> 0x0000555555555249 <+32>: pop %rbp</span></span><br><span class="line"><span class="comment"> 0x000055555555524a <+33>: ret </span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">inc</span><span class="params">(<span class="type">int</span> *value , <span class="type">int</span> add)</span> {</span><br><span class="line"> <span class="type">int</span> old;</span><br><span class="line"> __asm__ <span class="title function_">volatile</span> <span class="params">(</span></span><br><span class="line"><span class="params"> <span class="string">"lock; xaddl %2 , %1"</span></span></span><br><span class="line"><span class="params"> : <span class="string">"=a"</span> (old)</span></span><br><span class="line"><span class="params"> : <span class="string">"m"</span> (*value) , <span class="string">"a"</span> (add)</span></span><br><span class="line"><span class="params"> : <span class="string">"cc"</span> , <span class="string">"memory"</span></span></span><br><span class="line"><span class="params"> )</span>;</span><br><span class="line"> <span class="keyword">return</span> old;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在c++中<code>atomic</code>类提供了 <code>cas</code> 原子操作的方法如比较交互<code>std::atomic<T>::compare_exchange_strong</code></p><h2 id="cpu-亲和性"><a href="#cpu-亲和性" class="headerlink" title="cpu 亲和性"></a>cpu 亲和性</h2><p><strong>硬亲和性(affinity):</strong>简单来说就是利用 <code>linux</code> 内核提供给用户的 <code>API</code>,强行将进程或者线程绑定到某一个指定的 <code>cpu</code> 核运行。</p><p>在 Linux 内核中,所有的进程都有一个相关的数据结构,称为 <code>task_struct</code> <em>。</em>这个结构非常重要,原因有很多;其中与亲和性(affinity)相关度最高的是 <code>cpus_allowed</code> 位掩码。这个位掩码由<code> n</code> 位组成,与系统中的<code>n</code>个逻辑处理器一一对应。 具有 4 个物理 CPU 的系统可以有 4 位。如果这些 CPU 都启用了超线程,那么这个系统就有一个 8 位的位掩码。</p><p>如果为给定的进程设置了给定的位,那么这个进程就可以在相关的 <code>CPU</code> 上运行。因此,如果一个进程可以在任何 <code>CPU</code> 上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是 1。实际上,这就是 Linux 中进程的缺省状态。</p><p>Linux 内核 API 提供了一些方法,让用户可以修改位掩码或查看当前的位掩码:</p> <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"> <span class="meta">#<span class="keyword">define</span> _GNU_SOURCE <span class="comment">/* See feature_test_macros(7) */</span></span></span><br><span class="line"> <span class="meta">#<span class="keyword">include</span> <span class="string"><sched.h></span></span></span><br><span class="line"> <span class="type">int</span> <span class="title function_">sched_setaffinity</span><span class="params">(<span class="type">pid_t</span> pid, <span class="type">size_t</span> cpusetsize , <span class="type">const</span> <span class="type">cpu_set_t</span> *mask)</span>;</span><br><span class="line"><span class="comment">/*sched_setaffinity()将ID为pid的线程的CPU关联掩码设置为掩码指定的值。 </span></span><br><span class="line"><span class="comment">如果pid为零,则使用调用线程。 参数cpusetsize是掩码指向的数据的长度(字节)。 通常这个参数会被指定为sizeof(cpu_set_t)。*/</span></span><br><span class="line"> <span class="type">int</span> <span class="title function_">sched_getaffinity</span><span class="params">(<span class="type">pid_t</span> pid, <span class="type">size_t</span> cpusetsize , <span class="type">cpu_set_t</span> *mask)</span>;</span><br><span class="line"><span class="comment">/*sched_getaffinity()将ID为pid的线程的关联掩码写入掩码指向的cpu_set_t结构中。 cpusetsize参数指定掩码的大小(以字节为单位)。 如果pid为零,则返回调用线程的掩码。*/</span></span><br></pre></td></tr></table></figure><p>示例代码:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">process_affinity</span><span class="params">(<span class="type">int</span> num)</span> </span>{ <span class="comment">// 设置进程 cpu 亲和性</span></span><br><span class="line"> <span class="type">long</span> <span class="type">int</span> tid = <span class="built_in">syscall</span>(__NR_gettid);</span><br><span class="line"> std::cout << <span class="string">"tid : "</span> << tid << std::endl;</span><br><span class="line"> <span class="type">cpu_set_t</span> mask;</span><br><span class="line"> <span class="built_in">CPU_ZERO</span>(&mask);</span><br><span class="line"> <span class="built_in">CPU_SET</span>(tid % num , &mask);</span><br><span class="line"> <span class="built_in">sched_setaffinity</span>(tid , <span class="keyword">sizeof</span> mask , &mask);</span><br><span class="line">}</span><br><span class="line"><span class="comment">// 设置了进程的亲和性并不保证进程中创建的新线程在此cpu上运行,只能保证进程中的主线程在cpu上运行</span></span><br><span class="line"><span class="comment">// 本质就是 gettid,在对应cpu上执行对应 tid 的执行流</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">process_affinity</span><span class="params">(<span class="type">int</span> num)</span> </span>{ <span class="comment">// 设置线程 cpu 亲和性</span></span><br><span class="line"> std::cout << <span class="string">"tid : "</span> << tid << std::endl;</span><br><span class="line"> <span class="type">cpu_set_t</span> mask;</span><br><span class="line"> <span class="built_in">CPU_ZERO</span>(&mask);</span><br><span class="line"> <span class="built_in">CPU_SET</span>(tid % num , &mask);</span><br><span class="line"> <span class="built_in">pthread_setaffinity_np</span>(<span class="built_in">pthread_self</span>() , <span class="keyword">sizeof</span> mask , &mask);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">func</span><span class="params">(<span class="type">int</span> num)</span> </span>{</span><br><span class="line"> <span class="built_in">process_affinity</span>(num);</span><br><span class="line"> <span class="keyword">while</span> (<span class="number">1</span>) {}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> num = <span class="built_in">sysconf</span>(_SC_NPROCESSORS_CONF);</span><br><span class="line"> std::cout << num << std::endl;</span><br><span class="line"> <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span> ; i<num/<span class="number">2</span> ; ++i) {</span><br><span class="line"> <span class="function">std::thread <span class="title">t</span><span class="params">(func , num)</span></span>;</span><br><span class="line"> t.<span class="built_in">detach</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">usleep</span>(<span class="number">1000000000</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过 <code>htop</code> 可以看到在 <code>usleep</code> 结束之前有两个 cpu 的占用率是 100% 而其他线程占用几乎为 0,这说明两个线程分别绑定到了两个逻辑核心上</p><p>补充:</p><blockquote><p><code>main</code> 函数结束后相当于调用 <code>exit</code> ,这将导致整个进程结束,意味着其他线程如 detach 后的线程也跟着结束,由于这里尝试多次没有看到希望的效果,当然也可以使用 <code>posix_exit()</code> 令主线程退出,而不影响其他线程</p></blockquote><h2 id="无锁队列-zeromq"><a href="#无锁队列-zeromq" class="headerlink" title="无锁队列(zeromq)"></a>无锁队列(zeromq)</h2><h3 id="为什么需要无锁队列:"><a href="#为什么需要无锁队列:" class="headerlink" title="为什么需要无锁队列:"></a>为什么需要无锁队列:</h3><blockquote><p>Cache 损坏<br>拿互斥锁来说,采用休眠等待,线程被频繁抢占产生的Cache损坏将导致应用程序性能下降。</p><p>在同步机制上的争抢队列<br>由于锁机制,当资源争取发生的频率很高时,任务将大量的时间 (睡眠,等待,唤醒)浪费在获得保护队列数据的互斥锁,而不是处理队列中的数据上。</p><p>动态内存分配<br>当一个任务从堆中分配内存时,标准的内存分配机制会 阻塞所有与这个任务共享地址空间的其它任务(进程中的所有线程)</p></blockquote><h3 id="提供的原子操作"><a href="#提供的原子操作" class="headerlink" title="提供的原子操作"></a>提供的原子操作</h3><p><code>atom_op.h</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> CAS(a_ptr, a_oldVal, a_newVal) __sync_bool_compare_and_swap(a_ptr, a_oldVal, a_newVal)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AtomicAdd(a_ptr,a_count) __sync_fetch_and_add (a_ptr, a_count)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> AtomicSub(a_ptr,a_count) __sync_fetch_and_sub (a_ptr, a_count)</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sched.h></span> <span class="comment">// sched_yield()</span></span></span><br><span class="line"></span><br><span class="line">对应函数原型:</span><br><span class="line"><span class="type">bool</span> __sync_bool_compare_and_swap (T* __p, U __compVal, V __exchVal, ...);</span><br><span class="line">此函数将 __compVal 的值与 __p 指向的变量的值进行比较。 如果它们相等,那么 __exchVal 的值将存储在 __p指定的地址中; 否则,不会执行任何操作,如果 __compVal 的值与 __p 指向的变量的值相等,那么该函数返回 <span class="literal">true</span>; 否则,返回 <span class="literal">false</span>。</span><br><span class="line">T __sync_fetch_and_add ( T * __p , U __v , ...);</span><br><span class="line">该函数以原子方式将__v的值添加到__p指向的变量中。结果存储在__p指定的地址中,该函数返回__p指向的变量的初始值。</span><br></pre></td></tr></table></figure><p><code>atomic_ptr.hpp</code> 中对模板类型参数T的指针提供了原子操作,那其中一条进行分析</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">inline</span> T *<span class="title function_">cas</span> <span class="params">(T *cmp_, T *val_)</span> <span class="comment">// if(cmp == ptr) 则 ptr=val 返回旧值 , 不相等直接返回ptr</span></span><br><span class="line">{</span><br><span class="line">T *old;</span><br><span class="line"> __asm__ <span class="title function_">volatile</span> <span class="params">(</span></span><br><span class="line"><span class="params"> <span class="string">"lock; cmpxchg %2, %3"</span></span></span><br><span class="line"><span class="params"> : <span class="string">"=a"</span> (old), <span class="string">"=m"</span> (ptr)<span class="comment">// 输出操作数:old绑定到EAX寄存器,ptr绑定到内存位置</span></span></span><br><span class="line"><span class="params"> : <span class="string">"r"</span> (val_), <span class="string">"m"</span> (ptr), <span class="string">"0"</span> (cmp_) <span class="comment">// 输入操作数:val_可以使用任意寄存器,ptr绑定到内存位置,cmp_绑定到EAX寄存器</span></span></span><br><span class="line"><span class="params"> : <span class="string">"cc"</span>)</span>; <span class="comment">// 声明修改条件码寄存器</span></span><br><span class="line"> <span class="keyword">return</span> old;</span><br><span class="line">}</span><br><span class="line">private:</span><br><span class="line"><span class="keyword">volatile</span> T *ptr;</span><br></pre></td></tr></table></figure><h3 id="无锁队列的实现(单读单写)"><a href="#无锁队列的实现(单读单写)" class="headerlink" title="无锁队列的实现(单读单写)"></a>无锁队列的实现(单读单写)</h3><p><strong>yqueue 数据结构</strong></p><p>yqueue是一种高效的队列实现。 主要目标是最大限度地减少所需的分配/解除分配次数。 因此,yqueue以N批为单位分配/解除分配元素。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T, <span class="type">int</span> N></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">yqueue_t</span>;</span><br></pre></td></tr></table></figure><p>yqueue 内部是由一个个 chunk_t 组成的,使用双向链表的数据结构</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">chunk_t</span></span><br><span class="line">{</span><br><span class="line"> T values[N]; <span class="comment">//每个chunk_t可以容纳N个T类型的元素,以后就以一个chunk_t为单位申请内存</span></span><br><span class="line"> <span class="type">chunk_t</span> *prev;</span><br><span class="line"> <span class="type">chunk_t</span> *next;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>chunk_t的组织方式</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2024-06-30_00-50-29.png"></p><p><strong>ypipe</strong></p><p>ypipe_t在yqueue_t的基础上构建一个单写单读的无锁队列</p><p>ypipe内维护的成员变量:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> <span class="type">yqueue_t</span><T, N> queue;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Points to the first un-flushed item. This variable is used</span></span><br><span class="line"> <span class="comment">// exclusively by writer thread.</span></span><br><span class="line"> T *w; <span class="comment">//指向第一个未刷新的元素,只被写线程使用 要从哪里刷</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Points to the first un-prefetched item. This variable is used</span></span><br><span class="line"> <span class="comment">// exclusively by reader thread.</span></span><br><span class="line"> T *r; <span class="comment">//指向第一个还没预提取的元素,只被读线程使用</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Points to the first item to be flushed in the future.</span></span><br><span class="line"> T *f; <span class="comment">//指向下一轮要被刷新的一批元素中的第一个 要刷到哪里</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// The single point of contention between writer and reader thread.</span></span><br><span class="line"> <span class="comment">// Points past the last flushed item. If it is NULL,</span></span><br><span class="line"> <span class="comment">// reader is asleep. This pointer should be always accessed using</span></span><br><span class="line"> <span class="comment">// atomic operations.</span></span><br><span class="line"> <span class="type">atomic_ptr_t</span><T> c; <span class="comment">//读写线程共享的指针,指向每一轮刷新的起点。当c为空时,表示读线程睡眠(只会在读线程中被设置为空)</span></span><br></pre></td></tr></table></figure><p>构造函数:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">inline</span> <span class="title">ypipe_t</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// Insert terminator element into the queue.</span></span><br><span class="line"> queue.<span class="built_in">push</span>(); <span class="comment">//yqueue_t的尾指针加1,开始back_chunk为空,现在back_chunk指向第一个chunk_t块的第一个位置</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Let all the pointers to point to the terminator.</span></span><br><span class="line"> <span class="comment">// (unless pipe is dead, in which case c is set to NULL).</span></span><br><span class="line"> r = w = f = &queue.<span class="built_in">back</span>(); <span class="comment">//就是让r、w、f、c四个指针都指向这个end迭代器</span></span><br><span class="line"> c.<span class="built_in">set</span>(&queue.<span class="built_in">back</span>());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>write unwrite:写入可以单独写,也可以批量写。可以看到如果incomplete_ = true,则说明在批量写,直到incomplete_ = false时,进行写提交刷新 f 指针。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Write an item to the pipe. Don't flush it yet. If incomplete is</span></span><br><span class="line"><span class="comment">// set to true the item is assumed to be continued by items</span></span><br><span class="line"><span class="comment">// subsequently written to the pipe. Incomplete items are neverflushed down the stream.</span></span><br><span class="line"><span class="comment">// 写入数据,incomplete参数表示写入是否还没完成,在没完成的时候不会修改flush指针,即这部分数据不会让读线程看到。 </span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">write</span><span class="params">(<span class="type">const</span> T &value_, <span class="type">bool</span> incomplete_)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="comment">// Place the value to the queue, add new terminator element.</span></span><br><span class="line"> queue.<span class="built_in">back</span>() = value_;</span><br><span class="line"> queue.<span class="built_in">push</span>();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Move the "flush up to here" poiter.</span></span><br><span class="line"> <span class="keyword">if</span> (!incomplete_)</span><br><span class="line"> {</span><br><span class="line"> f = &queue.<span class="built_in">back</span>(); <span class="comment">// 记录要刷新的位置</span></span><br><span class="line"> <span class="comment">// printf("1 f:%p, w:%p\n", f, w);</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// printf("0 f:%p, w:%p\n", f, w);</span></span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Pop an incomplete item from the pipe. Returns true is such</span></span><br><span class="line"><span class="comment">// item exists, false otherwise.</span></span><br><span class="line"><span class="keyword">inline</span> <span class="type">bool</span> <span class="built_in">unwrite</span>(T *value_)</span><br><span class="line">{</span><br><span class="line"> <span class="keyword">if</span> (f == &queue.<span class="built_in">back</span>())</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line"> queue.<span class="built_in">unpush</span>();</span><br><span class="line"> *value_ = queue.<span class="built_in">back</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>flush</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Flush all the completed items into the pipe. Returns false if</span></span><br><span class="line"><span class="comment">// the reader thread is sleeping. In that case, caller is obliged to</span></span><br><span class="line"><span class="comment">// wake the reader up before using the pipe again.</span></span><br><span class="line"><span class="comment">// 刷新所有已经完成的数据到管道,返回false意味着读线程在休眠,在这种情况下调用者需要唤醒读线程。</span></span><br><span class="line"><span class="comment">// 批量刷新的机制, 写入批量后唤醒读线程;</span></span><br><span class="line"><span class="comment">// 反悔机制 unwrite</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">bool</span> <span class="title">flush</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">// If there are no un-flushed items, do nothing.</span></span><br><span class="line"> <span class="keyword">if</span> (w == f) <span class="comment">// 不需要刷新,即是还没有新元素加入</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Try to set 'c' to 'f'.</span></span><br><span class="line"> <span class="comment">// read时如果没有数据可以读取则c的值会被置为NULL</span></span><br><span class="line"> <span class="keyword">if</span> (c.<span class="built_in">cas</span>(w, f) != w) <span class="comment">// 尝试将c设置为f,即是准备更新w的位置</span></span><br><span class="line"> {<span class="comment">// 进入此分支:在flush之前进行了 check_read但是失败了,c被置为 NULL</span></span><br><span class="line"> <span class="comment">// Compare-and-swap was unseccessful because 'c' is NULL.</span></span><br><span class="line"> <span class="comment">// This means that the reader is asleep. Therefore we don't</span></span><br><span class="line"> <span class="comment">// care about thread-safeness and update c in non-atomic</span></span><br><span class="line"> <span class="comment">// manner. We'll return false to let the caller know</span></span><br><span class="line"> <span class="comment">// that reader is sleeping.</span></span><br><span class="line"> c.<span class="built_in">set</span>(f); <span class="comment">// 更新为新的f位置</span></span><br><span class="line"> w = f;</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">//线程看到flush返回false之后会发送一个消息给读线程,这需要写业务去做处理</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> <span class="comment">// 读端还有数据可读取</span></span><br><span class="line"> {</span><br><span class="line"> <span class="comment">// Reader is alive. Nothing special to do now. Just move</span></span><br><span class="line"> <span class="comment">// the 'first un-flushed item' pointer to 'f'.</span></span><br><span class="line"> w = f; <span class="comment">// 更新w的位置</span></span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以下参考文章中提供了多读多写的基于数据的无锁队列实现,以及理论分析</p><p>内联汇编:<a href="https://www.jianshu.com/p/1782e14a0766">https://www.jianshu.com/p/1782e14a0766</a></p><p>cas 单例模式:<a href="https://blog.csdn.net/q5707802/article/details/79251491">https://blog.csdn.net/q5707802/article/details/79251491</a></p><p><a href="https://www.yuque.com/linuxer/xngi03/cegcer#bgHkH">说说无锁(Lock-Free)编程那些事 (yuque.com)</a></p><p><a href="https://zhuanlan.zhihu.com/p/33985732">基于数组的无锁队列(译) - 知乎 (zhihu.com)</a></p>]]></content>
<summary type="html"><h1 id="CAS-与-无锁队列"><a href="#CAS-与-无锁队列" class="headerlink" title="CAS 与 无锁队列"></a>CAS 与 无锁队列</h1><p>假设我们有一全局变量 idx , 在执行 idx++ 时编译器会翻译为三条汇</summary>
<category term="从0到1" scheme="http://example.com/tags/%E4%BB%8E0%E5%88%B01/"/>
</entry>
<entry>
<title>io_uring</title>
<link href="http://example.com/2024/02/05/8.1io_uring/"/>
<id>http://example.com/2024/02/05/8.1io_uring/</id>
<published>2024-02-04T16:00:00.000Z</published>
<updated>2024-02-05T08:18:01.986Z</updated>
<content type="html"><![CDATA[<h1 id="io-uring"><a href="#io-uring" class="headerlink" title="io_uring"></a>io_uring</h1><p>在linux 5.1 版本之后,Linux内核提供了异步IO的框架支持,提供了三个系统调用 <code> io_uring_enter io_uring_register io_uring_setup</code> 在liburing.h头文件中对此系统调用进行了封装,如:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_enter</span><span class="params">(<span class="type">int</span> ring_fd, <span class="type">unsigned</span> <span class="type">int</span> to_submit,</span></span><br><span class="line"><span class="params"> <span class="type">unsigned</span> <span class="type">int</span> min_complete, <span class="type">unsigned</span> <span class="type">int</span> flags)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">return</span> (<span class="type">int</span>) syscall(__NR_io_uring_enter, ring_fd, to_submit, min_complete,</span><br><span class="line"> flags, <span class="literal">NULL</span>, <span class="number">0</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中系统调用号<code>__NR_io_uring_enter</code> 在<code>unistd_64.h</code>中被定义:<code>#define __NR_io_uring_enter 426</code></p><h2 id="性能:"><a href="#性能:" class="headerlink" title="性能:"></a>性能:</h2><p>由于调用系统调用时,会从用户态切换到内核态,从而进行上下文切换,而上下文切换会消耗一定的 CPU 时间。<code>io_uring</code> 为了减少或者摒弃系统调用,采用了用户态与内核态共享内存的方式来通信(用户态对共享内存进行读写操作是不需要使用系统调用的,所以不会发生上下文切换的情况)。</p><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_setup</span><span class="params">(u32 entries, <span class="keyword">struct</span> io_uring_params *p)</span>;</span><br></pre></td></tr></table></figure><p>用户通过调用 <code>io_uring_setup </code>初始化一个新的 <code>io_uring</code> 上下文。该函数返回一个 file descriptor,并将 <code>io_uring</code> 支持的功能、以及各个数据结构在 <code>fd</code> 中的偏移量存入 <code>params</code>。用户根据偏移量将 <code>fd</code> 映射到内存 (mmap) 后即可获得一块内核用户共享的内存区域。这块内存区域中,有 <code>io_uring</code> 的上下文信息:提交队列信息 (<code>SQ_RING</code>) 和完成队列信息 (<code>CQ_RING</code>);还有一块专门用来存放提交队列元素的区域 (SQEs)。<code>SQ_RING</code> 中只存储 SQE 在 SQEs 区域中的序号,<code>CQ_RING</code> 存储完整的任务完成数据。</p><p><img src="/img/v2-ad01522fd88442e9164001926b3d839c_r.png"></p><p><code>io_uring</code> 在创建时有两个选项(flag),对应着 <code>io_uring</code> 处理任务的不同方式:</p><ul><li>开启 <code>IORING_SETUP_IOPOLL</code> 后,<code>io_uring</code> 会使用轮询的方式执行所有的操作。</li><li>开启 <code>IORING_SETUP_SQPOLL</code> 后,<code>io_uring</code> 会创建一个内核线程专门用来收割用户提交的任务。</li><li>都不开启,通过 <code>io_uring_enter</code> 提交任务,收割任务无需 syscall。</li></ul><p><code>io_uring_setup</code> 设计的巧妙之处在于,内核通过一块和用户共享的内存区域进行消息的传递。在创建上下文后,任务提交、任务收割等操作都通过这块共享的内存区域进行,在 <code>IO_SQPOLL</code> 模式下,可以完全绕过 Linux 的 syscall 机制完成需要内核介入的操作(比如读写文件),大大减少了 syscall 切换上下文、刷 TLB 的开销。</p><h2 id="任务的定义"><a href="#任务的定义" class="headerlink" title="任务的定义"></a>任务的定义</h2><p><code>io_uring</code> 定义的异步 io 请求,对应宏定义</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> IORING_OP_NOP0</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> IORING_OP_READV1</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> IORING_OP_WRITEV2</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> IORING_OP_FSYNC3</span></span><br></pre></td></tr></table></figure><p>内核中定义了<code>io_op_defs</code>数组用与描述对应的异步 io 请求所需要的条件</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">io_op_def</span> <span class="title">io_op_defs</span>[] =</span> {</span><br><span class="line">[IORING_OP_NOP] = {},</span><br><span class="line">[IORING_OP_READV] = {</span><br><span class="line">.needs_file= <span class="number">1</span>,<span class="comment">//表示该操作需要文件描述符。</span></span><br><span class="line">.unbound_nonreg_file= <span class="number">1</span>, <span class="comment">//表示该操作需要非正则文件</span></span><br><span class="line">.pollin= <span class="number">1</span>,<span class="comment">//表示该操作需要poll in。</span></span><br><span class="line">.buffer_select= <span class="number">1</span>,<span class="comment">//表示该操作需要buffer_select</span></span><br><span class="line">.needs_async_setup= <span class="number">1</span>,<span class="comment">//表示该操作需要异步设置。</span></span><br><span class="line">.plug= <span class="number">1</span>, <span class="comment">//表示该操作需要plug。 </span></span><br><span class="line">.async_size= <span class="keyword">sizeof</span>(<span class="keyword">struct</span> io_async_rw),<span class="comment">//表示该操作的异步结构体大小</span></span><br><span class="line">},</span><br><span class="line">[IORING_OP_WRITEV] = {</span><br><span class="line">.needs_file= <span class="number">1</span>,</span><br><span class="line">.hash_reg_file= <span class="number">1</span>,</span><br><span class="line">.unbound_nonreg_file= <span class="number">1</span>,</span><br><span class="line">.pollout= <span class="number">1</span>,</span><br><span class="line">.needs_async_setup= <span class="number">1</span>,</span><br><span class="line">.plug= <span class="number">1</span>,</span><br><span class="line">.async_size= <span class="keyword">sizeof</span>(<span class="keyword">struct</span> io_async_rw),</span><br><span class="line">},</span><br><span class="line"> ....</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>io_uring</code> 中几乎每个操作都有对应的准备和执行函数。比如 <code>read</code> 操作就对应 <code>io_read_prep</code> 和 <code>io_read</code> 函数。除了同步操作,内核还支持异步调用的操作,对于这些操作,<code>io_uring</code>中还会有一个对应的异步准备函数以 <code>_async</code> 结尾</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">io_sendmsg_prep_async</span><span class="params">(<span class="keyword">struct</span> io_kiocb *req)</span></span><br><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">int</span> <span class="title function_">io_rw_prep_async</span><span class="params">(<span class="keyword">struct</span> io_kiocb *req, <span class="type">int</span> rw)</span>;</span><br></pre></td></tr></table></figure><h2 id="任务的创建"><a href="#任务的创建" class="headerlink" title="任务的创建"></a>任务的创建</h2><p><strong>用户将需要进行的操作写入 <code>io_uring</code> 的 SQ 中。在 CQ 中,用户可以收割任务的完成情况。</strong></p><p>sqe的结构:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring_sqe</span> {</span></span><br><span class="line">__u8opcode;<span class="comment">/* type of operation for this sqe */</span></span><br><span class="line">__u8flags;<span class="comment">/* IOSQE_ flags */</span></span><br><span class="line">__u16ioprio;<span class="comment">/* ioprio for the request */</span></span><br><span class="line">__s32fd;<span class="comment">/* file descriptor to do IO on */</span></span><br><span class="line">__u64off;<span class="comment">/* offset into file */</span></span><br><span class="line">__u64addr;<span class="comment">/* pointer to buffer or iovecs */</span></span><br><span class="line">__u32len;<span class="comment">/* buffer size or number of iovecs */</span></span><br><span class="line"><span class="class"><span class="keyword">union</span> {</span></span><br><span class="line"><span class="type">__kernel_rwf_t</span>rw_flags;</span><br><span class="line">__u32fsync_flags;</span><br><span class="line">__u16poll_events;</span><br><span class="line">__u32sync_range_flags;</span><br><span class="line">__u32msg_flags;</span><br><span class="line">__u32timeout_flags;</span><br><span class="line">};</span><br><span class="line">__u64user_data;<span class="comment">/* data to be passed back at completion time */</span></span><br><span class="line"><span class="class"><span class="keyword">union</span> {</span></span><br><span class="line">__u16buf_index;<span class="comment">/* index into fixed buffers, if used */</span></span><br><span class="line">__u64__pad2[<span class="number">3</span>];</span><br><span class="line">};</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>如要进行<code>readv</code> 操作:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">sqe->fd = filefd;<span class="comment">//需要操作的文件描述符</span></span><br><span class="line">sqe->flags = <span class="number">0</span>;</span><br><span class="line">sqe->opcode = IORING_OP_READV; <span class="comment">//readv对应option_code</span></span><br><span class="line">sqe->addr = &iovecs; <span class="comment">//存放的起始地址</span></span><br><span class="line">sqe->len = blocks;<span class="comment">//对应 iovec 数组的长度</span></span><br><span class="line">sqe->off = <span class="number">0</span>;<span class="comment">//从文件偏移位置为0处开始</span></span><br></pre></td></tr></table></figure><p>通常来说,使用 <code>io_uring</code> 的程序都需要用到 64 位的 <code>user_data</code> 来唯一标识一个操作。<code>user_data</code> 是 SQE 的一部分。<code>io_uring</code> 执行完某个操作后,会将这个操作的 <code>user_data</code> 和操作的返回值一起写入 CQ 中。一般携带指向堆内存的指针</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring_cqe</span> {</span></span><br><span class="line">__u64user_data;<span class="comment">/* sqe->data submission passed back */</span></span><br><span class="line">__s32res;<span class="comment">/* result code for this event */</span></span><br><span class="line">__u32flags;<span class="comment">// 未使用</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p> <code>io_ uring</code> 是一个异步接口,<code>errno</code> 将不用于传回错误信息。与此对应,<code>res</code> 将保存在成功的情况下等效的系统调用将要返回的内容,而在出错的情况下 <code>res</code> 将包含<code>-errno</code>。例如,如果正常读取系统调用返回<code>-1</code>并将<code>errno</code>设置为<code>EINVAL</code>,则<code>res</code>将包含<code>-EINVAL</code>。</p><h2 id="任务的提交与收割"><a href="#任务的提交与收割" class="headerlink" title="任务的提交与收割"></a>任务的提交与收割</h2><p><code>io_uring</code> 通过环形队列和用户交互。</p><p><img src="/img/v2-f688ede3a66c848fb4e3333767dfd9cc_r.png"></p><p>我们的先以用户提交任务为例,介绍 <code>io_uring</code> 的内核用户交互方式。用户提交任务的过程如下:</p><ul><li>将 SQE 写入 SQEs 区域,而后将 SQE 编号写入 SQ。(对应图中绿色第一步)</li><li>更新用户态记录的队头。(对应图中绿色第二步)</li><li>如果有多个任务需要同时提交,用户不断重复上面的过程。</li><li>将最终的队头编号写入与内核共享的 <code>io_uring</code> 上下文。(对应图中绿色第三步)</li></ul><p>接下来我们简要介绍内核获取任务、内核完成任务、用户收割任务的过程。</p><ul><li>内核态获取任务的方式是,从队尾读取 SQE,并更新 <code>io_uring</code> 上下文的 SQ tail。</li></ul><p><img src="/img/v2-670198a5e28380ee33809eec41d39e04_r.png"></p><ul><li>内核态完成任务:往 CQ 中写入 CQE,更新上下文 CQ head。</li><li>用户态收割任务:从 CQ 中读取 CQE,更新上下文 CQ tail。</li></ul><p><code>io_uring</code> 在创建时有两个选项,对应着 <code>io_uring</code> 处理任务的不同方式:</p><ul><li>开启 <code>IORING_SETUP_IOPOLL</code> 后,<code>io_uring</code> 会使用轮询的方式执行所有的操作。</li><li>开启 <code>IORING_SETUP_SQPOLL</code> 后,<code>io_uring</code> 会创建一个内核线程专门用来收割用户提交的任务。</li></ul><p>这些选项的设定会影响之后用户与 <code>io_uring</code> 交互的方式:</p><ul><li>都不开启,通过 <code>io_uring_enter</code> 提交任务,收割任务无需 syscall。</li><li>只开启 <code>IORING_SETUP_IOPOLL</code>,通过 <code>io_uring_enter</code> 提交任务和收割任务。</li><li>开启 <code>IORING_SETUP_SQPOLL</code>,无需任何 syscall 即可提交、收割任务。内核线程在一段时间无操作后会休眠,可以通过 <code>io_uring_enter</code> 唤醒。</li></ul><h2 id="liburing"><a href="#liburing" class="headerlink" title="liburing"></a>liburing</h2><p>使用流程:</p><ol><li><p>使用io_uring_queue_init,完成io_uring相关结构的初始化。在这个函数的实现中,会调用多个mmap来初始化一些内存。</p></li><li><p>初始化完成之后,为了提交IO请求,需要获取里面queue的一个项,使用io_uring_get_sqe。</p></li><li><p>获取到了空闲项之后,使用io_uring_prep_readv、io_uring_prep_writev初始化读、写请求。和前文所提preadv、pwritev的思想差不多,这里直接以不同的操作码委托io_uring_prep_rw,io_uring_prep_rw只是简单地初始化io_uring_sqe。</p></li><li><p>准备完成之后,使用io_uring_submit提交请求。</p></li><li><p>提交了IO请求时,可以通过非阻塞式函数io_uring_peek_cqe、阻塞式函数io_uring_wait_cqe获取请求完成的情况。默认情况下,完成的IO请求还会存在内部的队列中,需要通过io_uring_cqe_seen表标记完成操作。</p></li><li><p>使用完成之后要通过io_uring_queue_exit来完成资源清理的工作。</p></li></ol><h3 id="link-operation"><a href="#link-operation" class="headerlink" title="link operation"></a>link operation</h3><p>在io_uring中完成的任务并不是按照提交顺序返回的,有时我们需要按顺序的完成一组任务,这需要设置<code>sqe</code>对应的flag,为<code>flag</code>添加 <code>IOSQE_IO_LINK</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">io_uring_prep_write(sqe, fd, STR, <span class="built_in">strlen</span>(STR), <span class="number">0</span> );</span><br><span class="line">sqe->flags |= IOSQE_IO_LINK;<span class="comment">//添加link flag</span></span><br><span class="line"></span><br><span class="line">sqe = io_uring_get_sqe(ring);</span><br><span class="line"><span class="keyword">if</span> (!sqe) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Could not get SQE.\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">io_uring_prep_read(sqe, fd, buff, <span class="built_in">strlen</span>(STR),<span class="number">0</span>);</span><br><span class="line">sqe->flags |= IOSQE_IO_LINK;<span class="comment">//添加link flag</span></span><br><span class="line"></span><br><span class="line">sqe = io_uring_get_sqe(ring);</span><br><span class="line"><span class="keyword">if</span> (!sqe) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Could not get SQE.\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line">io_uring_prep_close(sqe, fd);</span><br><span class="line">io_uring_submit(ring);</span><br></pre></td></tr></table></figure><p><code>IOSQE_IO_LINK</code>使得本<code>sqe</code>与下一提交的<code>sqe</code>相关联,即两个任务之间有了先后顺序,如上代码就保证了,先读后写最后关闭</p><p>倘若我们操作的文件没有对应的权限,如没有写权限,文件以 O_WRONLY 打开,那么read操作将失败,这将导致后续link的操作全部失败</p><p>当涉及链接操作时,一个操作的失败将导致所有后续链接操作失败,并出现 errno“Operation cancelled”</p><h3 id="regster"><a href="#regster" class="headerlink" title="regster"></a>regster</h3><p>注册文件或用户缓冲区允许内核长期引用内部数据结构或创建应用程序内存的长期映射,从而大大减少每个I/O的开销。</p><p>应用程序可以增加或减少已注册缓冲区的大小或数量,方法是首先取消注册现有缓冲区,然后使用新缓冲区发出对io_uring_register()的新调用。注册缓冲区将等待环空闲。如果应用程序当前有正在处理的请求,注册将等待这些请求完成后再继续。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_register_buffers</span><span class="params">(<span class="keyword">struct</span> io_uring *ring, <span class="type">const</span> <span class="keyword">struct</span> iovec *iovecs, <span class="type">unsigned</span> nr_iovecs)</span>;</span><br><span class="line"><span class="type">void</span> <span class="title function_">io_uring_prep_write_fixed</span><span class="params">(<span class="keyword">struct</span> io_uring_sqe *sqe,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> fd,</span></span><br><span class="line"><span class="params"> <span class="type">const</span> <span class="type">void</span> *buf,</span></span><br><span class="line"><span class="params"> <span class="type">unsigned</span> nbytes,</span></span><br><span class="line"><span class="params"> __u64 offset,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> buf_index)</span>;</span><br><span class="line"> <span class="type">void</span> <span class="title function_">io_uring_prep_read_fixed</span><span class="params">(<span class="keyword">struct</span> io_uring_sqe *sqe,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> fd,</span></span><br><span class="line"><span class="params"> <span class="type">void</span> *buf,</span></span><br><span class="line"><span class="params"> <span class="type">unsigned</span> nbytes,</span></span><br><span class="line"><span class="params"> __u64 offset,</span></span><br><span class="line"><span class="params"> <span class="type">int</span> buf_index)</span>;</span><br></pre></td></tr></table></figure><p>在register后,对映射后的内存进行read / write 操作时,避免一次数据copy,register可以理解为将iovec mmap 到内核中,这样在进行read 或 write 后就少了一次copy</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> fd = open(FILE_NAME, O_RDWR|O_TRUNC|O_CREAT, <span class="number">0644</span>);</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < <span class="number">4</span>; i++) {</span><br><span class="line"> iov[i].iov_base = <span class="built_in">malloc</span>(BUF_SIZE);</span><br><span class="line"> iov[i].iov_len = BUF_SIZE;</span><br><span class="line"> <span class="built_in">memset</span>(iov[i].iov_base, <span class="number">0</span>, BUF_SIZE);</span><br><span class="line">}</span><br><span class="line"><span class="type">int</span> ret = io_uring_register_buffers(ring, iov, <span class="number">4</span>);</span><br><span class="line">sqe = io_uring_get_sqe(ring);</span><br><span class="line">io_uring_prep_write_fixed(sqe, fd, iov[<span class="number">0</span>].iov_base, str1_sz, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">io_uring_submit(ring);</span><br><span class="line">io_uring_wait_cqe(ring, &cqe);</span><br><span class="line">io_uring_cqe_seen(ring, cqe);</span><br></pre></td></tr></table></figure><p>注册文件</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_register_files</span><span class="params">(<span class="keyword">struct</span> io_uring *ring, <span class="type">const</span> <span class="type">int</span> *files , <span class="type">unsigned</span> nr_files)</span>;</span><br></pre></td></tr></table></figure><p>在用于提交的SQE中,您在使用文件描述符数组中的文件描述符索引而不是在像<code> io_uring_prep_readv()</code>和<code>io_uring_prep_writev()</code>这样的调用中使用文件描述符本身时设置了<code>IOSQE_FIXED_FILE</code> 标志。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> <span class="type">int</span> fds[<span class="number">2</span>];</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">io_uring_sqe</span> *<span class="title">sqe</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">io_uring_cqe</span> *<span class="title">cqe</span>;</span></span><br><span class="line"> <span class="type">int</span> ret = io_uring_register_files(ring, fds, <span class="number">1</span>);</span><br><span class="line"> sqe = io_uring_get_sqe(ring);</span><br><span class="line"> io_uring_prep_write(sqe, <span class="number">0</span>, buff1, str1_sz, <span class="number">0</span>);</span><br><span class="line"> <span class="comment">//这里的0表示注册的一组文件描述符的索引只有在设置flag |= IOSQE_FIXED_FILE 有效</span></span><br><span class="line"> sqe->flags |= IOSQE_FIXED_FILE;</span><br><span class="line">io_uring_submit(ring);</span><br></pre></td></tr></table></figure><h3 id="queue-polling"><a href="#queue-polling" class="headerlink" title="queue polling"></a>queue polling</h3><p>减少系统调用的数量是IO的一个主要目标。为此,<code>io_uring</code>允许提交I/O请求,而无需进行单个系统调用。这是通过<code>io_uring</code>支持的一个特殊的提交队列轮询特性完成的。在这种模式下,在程序设置轮询模式后,<code>o_uring</code>启动一个特殊的内核线程,该线程轮询共享提交队列中程序可能添加的条目。这样,您只需将条目提交到共享队列中,内核线程应该看到它并拾取提交队列条目,而无需您的程序进行io_uring_enter()系统调用。这是在用户空间和内核之间共享队列的一个好处。</p><p>通过在io_uring_params结构的flags成员中设置IORING_SETUP_SQPOLL标志,可以告诉io_uring要使用此模式。如果内核线程在一段时间内没有看到任何提交,它将退出,需要再次调用io_uring_enter()系统调用来唤醒内核线程,这里的时间由 io_uring_param 的成员 <code>sq_thread_idle</code> 所决定</p><p>在使用liburing时,您永远不会直接调用 <code> io_uring_enter()</code>系统调用。这通常是由<code>liburing</code>的<code> io_uring_submit()</code>函数来处理的。它会自动判断你是否在使用轮询模式,并处理你的程序何时需要调用<code> io_uring_enter()</code>,而你不必为此费心。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring</span> <span class="title">ring</span>;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring_params</span> <span class="title">params</span>;</span></span><br><span class="line"><span class="built_in">memset</span>(&params, <span class="number">0</span>, <span class="keyword">sizeof</span>(params));</span><br><span class="line">params.flags |= IORING_SETUP_SQPOLL;<span class="comment">// 设置poll模式</span></span><br><span class="line">params.sq_thread_idle = <span class="number">2000</span>;</span><br><span class="line">io_uring_queue_init_params(<span class="number">8</span>, &ring, &params);</span><br></pre></td></tr></table></figure><h3 id="eventfd"><a href="#eventfd" class="headerlink" title="eventfd"></a>eventfd</h3><p>首先我们回顾下 eventfd 系统调用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">eventfd</span><span class="params">(<span class="type">unsigned</span> <span class="type">int</span> initval, <span class="type">int</span> flags)</span>;</span><br></pre></td></tr></table></figure><p>读操作:</p><p>每次成功的 read(2) 操作都会返回一个 8 字节的整数。如果提供的缓冲区大小小于 8 字节,则 read(2) 会失败并返回错误 EINVAL。</p><p>read(2) 返回的值采用主机字节序,即主机上用于整数的本机字节序。</p><p>read(2) 的行为取决于 eventfd 计数器当前是否具有非零值以及创建 eventfd 文件描述符时是否指定了 EFD_SEMAPHORE 标志:</p><ul><li><p>如果未指定 EFD_SEMAPHORE 并且 eventfd 计数器具有非零值,则 read(2) 会返回 8 字节的数据,其中包含该值,并将计数器的值重置为零。</p></li><li><p>如果指定了 EFD_SEMAPHORE 并且 eventfd 计数器具有非零值,则 read(2) 会返回 8 字节的数据,其中包含值 1,并将计数器的值减 1。</p></li><li><p>如果在调用 read(2) 时 eventfd 计数器为零,则调用会阻塞,直到计数器变为非零(此时 read(2) 会按上述方式进行);如果文件描述符已被设置为非阻塞,则会失败并返回错误 EAGAIN。</p></li></ul><p>写操作:</p><p>write(2) 调用会将其缓冲区中提供的 8 字节整数值<strong>添加</strong>到计数器中。计数器中可以存储的最大值是最大无符号 64 位值减 1(即 0xfffffffffffffffe)。如果加法会导致计数器的值超过最大值,那么 write(2) 会阻塞,直到对文件描述符执行 read(2) 操作,或者如果文件描述符已被设置为非阻塞,则会失败并返回错误 EAGAIN。</p><p>liburing提供了封装</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">io_uring_register_eventfd</span><span class="params">(<span class="keyword">struct</span> io_uring *ring, <span class="type">int</span> fd)</span>;</span><br></pre></td></tr></table></figure><p><code>io_uring_register_eventfd</code> 将eventfd的文件描述符fd注册到io_uring环上,当完成队列中有事件时,会对event执行write操作</p><p>如果不再需要通知,可以调用io_uring_unregister_eventfd(3)来删除eventfd注册。 不需要eventfd参数,因为一个环只能注册一个eventfd。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> BUFF_SZ 512</span></span><br><span class="line"></span><br><span class="line"><span class="type">char</span> buff[BUFF_SZ + <span class="number">1</span>];</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">io_uring</span> <span class="title">ring</span>;</span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">error_exit</span><span class="params">(<span class="type">char</span> *message)</span> {</span><br><span class="line"> perror(message);</span><br><span class="line"> <span class="built_in">exit</span>(EXIT_FAILURE);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> *<span class="title function_">listener_thread</span><span class="params">(<span class="type">void</span> *data)</span> {</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">io_uring_cqe</span> *<span class="title">cqe</span>;</span></span><br><span class="line"> <span class="type">int</span> efd = (<span class="type">int</span>) data;</span><br><span class="line"> <span class="type">eventfd_t</span> v;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%s: Waiting for completion event...\n"</span>, __FUNCTION__);</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> ret = eventfd_read(efd, &v); <span class="comment">//首次调用会 block , 可读以为这有事件完成了</span></span><br><span class="line"> <span class="keyword">if</span> (ret < <span class="number">0</span>) error_exit(<span class="string">"eventfd_read"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%s: Got completion event.\n"</span>, __FUNCTION__);</span><br><span class="line"></span><br><span class="line"> ret = io_uring_wait_cqe(&ring, &cqe);</span><br><span class="line"> <span class="keyword">if</span> (ret < <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Error waiting for completion: %s\n"</span>,</span><br><span class="line"> strerror(-ret));</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">/* Now that we have the CQE, let's process it */</span></span><br><span class="line"> <span class="keyword">if</span> (cqe->res < <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Error in async operation: %s\n"</span>, strerror(-cqe->res));</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Result of the operation: %d\n"</span>, cqe->res);</span><br><span class="line"> io_uring_cqe_seen(&ring, cqe);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"Contents read from file:\n%s\n"</span>, buff);</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">setup_io_uring</span><span class="params">(<span class="type">int</span> efd)</span> {</span><br><span class="line"> <span class="type">int</span> ret = io_uring_queue_init(<span class="number">8</span>, &ring, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (ret) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Unable to setup io_uring: %s\n"</span>, strerror(-ret));</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> io_uring_register_eventfd(&ring, efd);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">read_file_with_io_uring</span><span class="params">()</span> {</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">io_uring_sqe</span> *<span class="title">sqe</span>;</span></span><br><span class="line"></span><br><span class="line"> sqe = io_uring_get_sqe(&ring);</span><br><span class="line"> <span class="keyword">if</span> (!sqe) {</span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"Could not get SQE.\n"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> fd = open(<span class="string">"/etc/passwd"</span>, O_RDONLY);</span><br><span class="line"> io_uring_prep_read(sqe, fd, buff, BUFF_SZ, <span class="number">0</span>);</span><br><span class="line"> io_uring_submit(&ring);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">pthread_t</span> t;</span><br><span class="line"> <span class="type">int</span> efd;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Create an eventfd instance */</span></span><br><span class="line"> efd = eventfd(<span class="number">0</span>, <span class="number">0</span>); <span class="comment">//创建eventfd</span></span><br><span class="line"> <span class="keyword">if</span> (efd < <span class="number">0</span>)</span><br><span class="line"> error_exit(<span class="string">"eventfd"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Create the listener thread */</span></span><br><span class="line"> pthread_create(&t, <span class="literal">NULL</span>, listener_thread, (<span class="type">void</span> *)efd);</span><br><span class="line"></span><br><span class="line"> sleep(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Setup io_uring instance and register the eventfd */</span></span><br><span class="line"> setup_io_uring(efd);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Initiate a read with io_uring */</span></span><br><span class="line"> read_file_with_io_uring();</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* Wait for th listener thread to complete */</span></span><br><span class="line"> pthread_join(t, <span class="literal">NULL</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">/* All done. Clean up and exit. */</span></span><br><span class="line"> io_uring_queue_exit(&ring);</span><br><span class="line"> <span class="keyword">return</span> EXIT_SUCCESS;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>参考:</p><p><a href="https://unixism.net/loti/index.html">https://unixism.net/loti/index.html</a></p><p><a href="https://zhuanlan.zhihu.com/p/361955546">浅析开源项目之io_uring - 知乎 (zhihu.com)</a></p><p><a href="https://mp.weixin.qq.com/s/1wZpFhwJR-LNkQm-QzFxRQ">图解原理|Linux I/O 神器之 io_uring (qq.com)</a></p><p><a href="https://zhuanlan.zhihu.com/p/380726590">https://zhuanlan.zhihu.com/p/380726590</a></p><p><a href="https://zhuanlan.zhihu.com/p/334658432">https://zhuanlan.zhihu.com/p/334658432</a></p>]]></content>
<summary type="html"><h1 id="io-uring"><a href="#io-uring" class="headerlink" title="io_uring"></a>io_uring</h1><p>在linux 5.1 版本之后,Linux内核提供了异步IO的框架支持,提供了三个系统调用 </summary>
<category term="从0到1" scheme="http://example.com/tags/%E4%BB%8E0%E5%88%B01/"/>
</entry>
<entry>
<title>线程和原子操作</title>
<link href="http://example.com/2024/01/18/3.2%E7%BA%BF%E7%A8%8B%E4%B8%8E%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C/"/>
<id>http://example.com/2024/01/18/3.2%E7%BA%BF%E7%A8%8B%E4%B8%8E%E5%8E%9F%E5%AD%90%E6%93%8D%E4%BD%9C/</id>
<published>2024-01-17T16:00:00.000Z</published>
<updated>2024-03-15T13:28:03.252Z</updated>
<content type="html"><![CDATA[<h1 id="线程和原子操作"><a href="#线程和原子操作" class="headerlink" title="线程和原子操作"></a>线程和原子操作</h1><h2 id="线程thread基本使用"><a href="#线程thread基本使用" class="headerlink" title="线程thread基本使用"></a>线程thread基本使用</h2><p>动或按值复制线程函数的参数。如果需要传递引用参数给线程函数,那么必须包装它(例如用 <a href="https://zh.cppreference.com/w/cpp/utility/functional/ref">std::ref</a> 或 <a href="https://zh.cppreference.com/w/cpp/utility/functional/ref">std::cref</a>)。</p><p>忽略来自函数的任何返回值。如果函数抛出异常,那么就会调用 <a href="https://zh.cppreference.com/w/cpp/error/terminate">std::terminate</a>。<strong>需要将返回值或异常传递回调用方线程时可以使用 <a href="https://zh.cppreference.com/w/cpp/thread/promise">std::promise</a> 或 <a href="https://zh.cppreference.com/w/cpp/thread/async">std::async</a>。</strong></p><p>传值和传引用:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">func1</span><span class="params">(<span class="type">int</span> a , <span class="type">int</span> b)</span> </span>{</span><br><span class="line">cout << a +b <<endl;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">func2</span><span class="params">(<span class="type">int</span> & a)</span> </span>{</span><br><span class="line">a += <span class="number">10</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="function">std::thread <span class="title">t1</span><span class="params">(func1 , <span class="number">10</span> , <span class="number">20</span>)</span></span>;</span><br><span class="line"><span class="type">int</span> c = <span class="number">10</span></span><br><span class="line">std::thread <span class="built_in">t2</span>(func2 , std::<span class="built_in">ref</span>(c));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>std::ref</code> 用于包装按引用传递的值。<br><code>std::cref</code> 用于包装按<code>const</code>引用传递的值。</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-10-11_12-07-38.png"></p><p>创建一个可运行(<strong>创建时传入线程函数</strong>)的线程对象后,必须对该线程对象进行处理,<strong>要么调用join(),要么调用detach()<strong>,否则线程对象析构时</strong>程序</strong>将直接退出。</p><blockquote><p><code>std::thread::~thread</code>销毁 thread 对象。若 *this 拥有关联线程( joinable() == true ),则调用std::terminate。</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(<span class="type">int</span> a)</span> </span>{ cout << a << endl;}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line">{</span><br><span class="line"><span class="function">thread <span class="title">t1</span><span class="params">(f,<span class="number">1</span>)</span></span>;</span><br><span class="line">}</span><br><span class="line"> this_thread::<span class="built_in">sleep_for</span>(std::chrono::<span class="built_in">seconds</span>(<span class="number">2</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如上代码将收到<code>Abort</code>信号,因为,线程对象t1在作用域中没有调用join或detach,当t1出作用域时,将会抛出异常导致程序退出</p><h3 id="线程遇到重载函数"><a href="#线程遇到重载函数" class="headerlink" title="线程遇到重载函数"></a>线程遇到重载函数</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(<span class="type">int</span> a)</span> </span>{ std:: cout << a << std::endl; }</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">(std::string a)</span> </span>{ std::cout <<a << std::endl;}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">std::thread <span class="title">t1</span><span class="params">((<span class="type">void</span>(*)(<span class="type">int</span>))f , <span class="number">1</span>)</span></span>;</span><br><span class="line"> <span class="function">std::thread <span class="title">t2</span><span class="params">((<span class="type">void</span>(*)(std::string))f , <span class="string">"aaa"</span>)</span></span>;</span><br><span class="line"> t1.<span class="built_in">join</span>();</span><br><span class="line"> t2.<span class="built_in">join</span>();</span><br><span class="line"> std::this_thread::<span class="built_in">sleep_for</span>(std::chrono::<span class="built_in">seconds</span>(<span class="number">1</span>));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="原子变量"><a href="#原子变量" class="headerlink" title="原子变量"></a>原子变量</h2><p>每个 <code>std::atomic</code> 模板的实例化和全特化定义一个原子类型。如果一个线程写入原子对象,同时另一线程从它读取,那么行为良好定义,<code>std::atomic</code> 既不可复制也不可移动。</p><p><code>std::atomic</code>模板可用于任何满足可复制构造,可复制赋值,可平凡复制类型T的特化,不支持复制初始化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">std::is_trivially_copyable<T>::value</span><br><span class="line">std::is_copy_constructible<T>::value</span><br><span class="line">std::is_move_constructible<T>::value</span><br><span class="line">std::is_copy_assignable<T>::value</span><br><span class="line">std::is_move_assignable<T>::value</span><br></pre></td></tr></table></figure><p>上述值为<code>false</code>非良构</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::atomic<<span class="type">int</span>> <span class="title">foo</span><span class="params">(<span class="number">0</span>)</span></span>; <span class="comment">//直接初始化</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">set_foo</span><span class="params">(<span class="type">int</span> x)</span> </span>{</span><br><span class="line"> foo.<span class="built_in">store</span>(x , std::memory_order_relaxed);</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">print_foo</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> x;</span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> x = foo.<span class="built_in">load</span>(std::memory_order_relaxed);</span><br><span class="line"> } <span class="keyword">while</span>(x == <span class="number">0</span>);</span><br><span class="line"> std::cout <<<span class="string">"foo :"</span> <<x << <span class="string">'\n'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">std::thread <span class="title">first</span><span class="params">(print_foo)</span></span>;</span><br><span class="line"> <span class="function">std::thread <span class="title">second</span><span class="params">(set_foo , <span class="number">10</span>)</span></span>;</span><br><span class="line"> first.<span class="built_in">join</span>();</span><br><span class="line"> second.<span class="built_in">join</span>();</span><br><span class="line"> std::cout << <span class="string">"main finish \n"</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>std::atomic<T>::store</li></ul><p><code>void store(T desired , std::memort_order order = std::memory_order_seq_cst) noexcept</code>以 desired 原子地替换当前值。按照 order 的值影响内存。order必须是 std::memory_order_relaxed . std::memory_order_release , std::memory_order_seq_cst</p><ul><li>std::atomic<T>::load</li></ul><p><code>void load(std::memort_order order = std::memory_order_seq_cst) const noexcept</code>,原子地加载并返回原子变量的当前值。按照 order 的值影响内存。返回原子变量的当前值。</p><ul><li>std::atomic<T>::operator=</li></ul><p><code>T operator=( T desired ) noexcept;</code>将 desired 原子地赋给原子变量。等价于 store(desired)。返回 desired</p><h3 id="std-call-once-std-once-flag"><a href="#std-call-once-std-once-flag" class="headerlink" title="std::call_once std::once_flag"></a>std::call_once std::once_flag</h3><p>定义:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span>< <span class="keyword">class</span> Callable, <span class="keyword">class</span>... Args ></span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">call_once</span><span class="params">(std::once_flag& flag , Callable&& f , Args&&... args)</span></span>;</span><br></pre></td></tr></table></figure><ul><li><p>如果在调用 <code>std::call_once</code> 的时刻,flag 指示 f 已经调用过,那么 <code>std::call_once</code> 会立即返回(称这种对 <code>std::call_once</code> 的调用为<em>消极</em>)。</p></li><li><p>否则,std::call_once 会调用 INVOKE(std::forward<Callable>(f), std::forward<Args>(args)…)。与 std::thread 的构造函数或 std::async 不同,不会移动或复制参数,因为不需要转移它们到另一执行线程(称这种对 std::call_once 的调用为积极)。</p><ul><li>如果该调用抛出了异常,那么将异常传播给 <code>std::call_once</code> 的调用方,并且不翻转 flag,这样还可以尝试后续调用(称这种对 <code>std::call_once</code> 的调用为<em>异常</em>)。</li><li>如果该调用正常返回(称这种对 <code>std::call_once</code> 的调用为<em>返回</em>),那么翻转 flag,并保证以同一 flag 对 <code>std::call_once</code> 的其他调用为<em>消极</em>。</li></ul></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><mutex></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><thread></span></span></span><br><span class="line"> </span><br><span class="line">std::once_flag flag1, flag2;</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">simple_do_once</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> std::<span class="built_in">call_once</span>(flag1, [](){ std::cout << <span class="string">"简单样例:调用一次\n"</span>; });</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">may_throw_function</span><span class="params">(<span class="type">bool</span> do_throw)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (do_throw)</span><br><span class="line"> {</span><br><span class="line"> std::cout << <span class="string">"抛出:call_once 会重试\n"</span>; <span class="comment">// 这会出现不止一次</span></span><br><span class="line"> <span class="keyword">throw</span> std::<span class="built_in">exception</span>();</span><br><span class="line"> }</span><br><span class="line"> std::cout << <span class="string">"没有抛出,call_once 不会再重试\n"</span>; <span class="comment">// 保证一次</span></span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">do_once</span><span class="params">(<span class="type">bool</span> do_throw)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">try</span></span><br><span class="line"> {</span><br><span class="line"> std::<span class="built_in">call_once</span>(flag2, may_throw_function, do_throw);</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">catch</span> (...) {}</span><br><span class="line">}</span><br><span class="line"> </span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="function">std::thread <span class="title">st1</span><span class="params">(simple_do_once)</span></span>;</span><br><span class="line"> <span class="function">std::thread <span class="title">st2</span><span class="params">(simple_do_once)</span></span>;</span><br><span class="line"> <span class="function">std::thread <span class="title">st3</span><span class="params">(simple_do_once)</span></span>;</span><br><span class="line"> <span class="function">std::thread <span class="title">st4</span><span class="params">(simple_do_once)</span></span>;</span><br><span class="line"> st1.<span class="built_in">join</span>();</span><br><span class="line"> st2.<span class="built_in">join</span>();</span><br><span class="line"> st3.<span class="built_in">join</span>();</span><br><span class="line"> st4.<span class="built_in">join</span>();</span><br><span class="line"> </span><br><span class="line"> <span class="function">std::thread <span class="title">t1</span><span class="params">(do_once, <span class="literal">true</span>)</span></span>;</span><br><span class="line"> <span class="function">std::thread <span class="title">t2</span><span class="params">(do_once, <span class="literal">true</span>)</span></span>;</span><br><span class="line"> <span class="function">std::thread <span class="title">t3</span><span class="params">(do_once, <span class="literal">false</span>)</span></span>;</span><br><span class="line"> <span class="function">std::thread <span class="title">t4</span><span class="params">(do_once, <span class="literal">true</span>)</span></span>;</span><br><span class="line"> t1.<span class="built_in">join</span>();</span><br><span class="line"> t2.<span class="built_in">join</span>();</span><br><span class="line"> t3.<span class="built_in">join</span>();</span><br><span class="line"> t4.<span class="built_in">join</span>();</span><br><span class="line">}</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">简单样例:调用一次 </span></span><br><span class="line"><span class="comment">抛出:call_once 会重试</span></span><br><span class="line"><span class="comment">抛出:call_once 会重试</span></span><br><span class="line"><span class="comment">抛出:call_once 会重试</span></span><br><span class="line"><span class="comment">没有抛出,call_once 不会再重试</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><h2 id="异步操作"><a href="#异步操作" class="headerlink" title="异步操作"></a>异步操作</h2><h3 id="future"><a href="#future" class="headerlink" title="future"></a>future</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>< <span class="keyword">class</span> <span class="title class_">T</span> > <span class="keyword">class</span> <span class="title class_">future</span>;</span><br><span class="line"><span class="keyword">template</span>< <span class="keyword">class</span> <span class="title class_">T</span> > <span class="keyword">class</span> <span class="title class_">future</span><T&>;</span><br><span class="line"><span class="keyword">template</span><> <span class="keyword">class</span> <span class="title class_">future</span><<span class="type">void</span>>;</span><br><span class="line"><span class="comment">//asynchronous 异步</span></span><br><span class="line"><span class="comment">//future 未来</span></span><br><span class="line"><span class="comment">//promise 承诺</span></span><br></pre></td></tr></table></figure><p>类模板 <code>std::future</code> 提供访问异步操作<strong>结果</strong>的机制</p><p>通过<code>std::async std::packaged_task std::promise</code>创建的异步操作能提供一个<code>std::future</code>对象给该异步操作的创建者,然后,异步操作的创建者能用各种方法查询 等待 或从<code>std::future</code>中提取值。若异步操作认为提供值,则这些方法可能阻塞。异步操作准备好发送结果给创建者时,它能通过修改链接到创建者的 <code>std::future</code> 的共享状态,<code>std::future</code> 所引用的共享状态不与另一异步返回对象共享</p><ul><li>get():<code>get</code> 方法等待直至 <code>future</code> 拥有合法结果并(依赖于使用哪个模板)获取它。它等效地调用 <a href="https://zh.cppreference.com/w/cpp/thread/future/wait">wait()</a> 等待结果,泛型模板和二个模板特化各含单个 <code>get</code> 版本。 <code>get</code> 的三个版本仅在返回类型有别。若调用此函数前 <a href="https://zh.cppreference.com/w/cpp/thread/future/valid">valid()</a> 为 false 则行为未定义。</li></ul><h3 id="async"><a href="#async" class="headerlink" title="async"></a>async</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>< <span class="keyword">class</span> <span class="title class_">Function</span>, <span class="keyword">class</span>... Args ></span><br><span class="line">std::future<<span class="keyword">typename</span> std::result_of<<span class="keyword">typename</span> std::decay<Function>::<span class="built_in">type</span>(</span><br><span class="line"> <span class="keyword">typename</span> std::decay<Args>::type...)>::type></span><br><span class="line"> <span class="built_in">async</span>( Function&& f, Args&&... args );</span><br></pre></td></tr></table></figure><p><strong>函数模板</strong> <code>std::async</code> 异步地运行函数 f(有可能在可能是线程池一部分的分离线程中),并返回最终将保有该函数调用结果的<code>std::future</code></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">find_result_to_add</span><span class="params">()</span> </span>{</span><br><span class="line"> std::this_thread::<span class="built_in">sleep_for</span>(std::chrono::<span class="built_in">seconds</span>(<span class="number">2</span>)); <span class="comment">// 用来测试异步延迟的影响</span></span><br><span class="line"> std::cout << <span class="string">"find_result_to_add"</span> << std::endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">1</span> + <span class="number">1</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> std::future<<span class="type">int</span>> result = std::<span class="built_in">async</span>(find_result_to_add);</span><br><span class="line"> cout << <span class="string">"valid:"</span><< result.<span class="built_in">valid</span>() << std::endl;</span><br><span class="line"><span class="comment">// std::future<decltype (find_result_to_add())> result = std::async(find_result_to_add);</span></span><br><span class="line"><span class="comment">// auto result = std::async(find_result_to_add); // 推荐的写法</span></span><br><span class="line"> <span class="built_in">do_other_things</span>();</span><br><span class="line"> std::cout << <span class="string">"result: "</span> << result.<span class="built_in">get</span>() << std::endl; <span class="comment">// 延迟是否有影响?</span></span><br><span class="line"> cout << <span class="string">"valid:"</span><< result.<span class="built_in">valid</span>() << std::endl;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="packaged-task"><a href="#packaged-task" class="headerlink" title="packaged_task"></a>packaged_task</h3><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>< <span class="keyword">class</span> <span class="title class_">R</span>, <span class="keyword">class</span> ...Args ></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">packaged_task</span><<span class="built_in">R</span>(Args...)>;</span><br></pre></td></tr></table></figure><p><strong>类模板</strong> std::packaged_task 包装任何可调用 (Callable) 目标(函数、 lambda 表达式、 bind 表达式或其他函数对象),使得能异步调用它。<strong>其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中</strong>。</p><p><code>std::function</code> <code>std::packaged_task</code> 是多态、具分配器的容器:可在堆上或以提供的分配器分配存储的可调用对象。</p><ul><li><p>operator():如果以<code>INVOKE<R>(f ,args...)</code>调用存储的任务 f。任务返回值或任何抛出的异常被存储于共享状态。令共享状态就绪,并解除阻塞任何等待此操作的线程。</p></li><li><p>get_future():返回与 *this 共享同一共享状态的 <code>future</code> 。<code>get_future</code> 只能对每个 <code>packaged_task</code> 调用一次。</p></li><li><p>reset():重置状态,抛弃先前执行的结果。构造共享状态。等价于 *this = packaged_task(std::move(f)) ,其中 <code>f</code> 是存储的任务。</p></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">add</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b, <span class="type">int</span> c)</span> </span>{</span><br><span class="line">std::cout << <span class="string">"call add\n"</span>;</span><br><span class="line"><span class="keyword">return</span> a + b + c;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">do_other_things</span><span class="params">()</span> </span>{</span><br><span class="line">std::cout << <span class="string">"do_other_things"</span> << std::endl;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="function">std::packaged_task<<span class="title">int</span><span class="params">(<span class="type">int</span>, <span class="type">int</span>, <span class="type">int</span>)</span>> <span class="title">task</span><span class="params">(add)</span></span>; <span class="comment">// 封装任务,不运行</span></span><br><span class="line"><span class="built_in">do_other_things</span>();</span><br><span class="line">std::future<<span class="type">int</span>> result = task.<span class="built_in">get_future</span>();<span class="comment">//将result与task的future关联,不运行</span></span><br><span class="line"><span class="built_in">task</span>(<span class="number">1</span>, <span class="number">1</span>, <span class="number">2</span>); <span class="comment">//任务执行,否则在get()获取future的值时会一直阻塞</span></span><br><span class="line">std::cout << <span class="string">"result:"</span> << result.<span class="built_in">get</span>() << std::endl;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="promise"><a href="#promise" class="headerlink" title="promise"></a>promise</h3><p>传统的线程返回值:传递一个指针给线程,表示该线程将会把返回值写入指针指向的内存空间。此时主线程将用条件变量等待值被写入,当线程把值写入指针指定的内存后,将唤醒(signal)条件变量,然后主线程将被唤醒,然后从指针指向的内存中获取返回值。</p><p>为了实现获取一个返回值的需求,使用传统的方法,我们需要条件变量(condition variable), 互斥量(mutex),和指针三个对象。</p><p>C++11的方法:使用<strong>std::future</strong>和<strong>std::promise</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span>< <span class="keyword">class</span> <span class="title class_">R</span> > <span class="keyword">class</span> <span class="title class_">promise</span>;</span><br></pre></td></tr></table></figure><p>类模板 <code>std::promise</code> 提供存储值或异常的设施,之后通过 <code>std::promise</code> 对象所创建的 <code>std::future</code>对象异步获得结果。注意 <code>std::promise</code> 只应当使用一次。</p><ul><li><p>std::future<T> get_future:返回与 *this 关联同一状态的 future 对象。若 *this 无共享状态,或已调用 <code>get_future</code> 则抛出异常</p></li><li><p>set_value(const R& value):原子地存储 <code>value</code> 到共享状态,并令状态就绪。</p></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">print</span><span class="params">(std::promise<std::string>& p)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">p.<span class="built_in">set_value</span>(<span class="string">"There is the result whitch you want."</span>);<span class="comment">//设置线程返回值</span></span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">std::promise<std::string> promise;</span><br><span class="line">std::future<std::string> result = promise.<span class="built_in">get_future</span>();<span class="comment">//将promise中的future与result相关联</span></span><br><span class="line"><span class="function">std::thread <span class="title">t</span><span class="params">(print, std::ref(promise))</span></span>; </span><br><span class="line"> <span class="comment">//新建线程,并传入 promise 的 引用,promise 无法复制故要传入引用</span></span><br><span class="line"><span class="built_in">do_some_other_things</span>();</span><br><span class="line">std::cout << result.<span class="built_in">get</span>() << std::endl;<span class="comment">//从result中获取结果</span></span><br><span class="line">t.<span class="built_in">join</span>();</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="线程和原子操作"><a href="#线程和原子操作" class="headerlink" title="线程和原子操作"></a>线程和原子操作</h1><h2 id="线程thread基本使用"><a href="#线程thread基本使用" class="</summary>
<category term="从0到1" scheme="http://example.com/tags/%E4%BB%8E0%E5%88%B01/"/>
</entry>
<entry>
<title>网络编程实战</title>
<link href="http://example.com/2024/01/16/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%AE%9E%E6%88%98/"/>
<id>http://example.com/2024/01/16/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%AE%9E%E6%88%98/</id>
<published>2024-01-15T16:00:00.000Z</published>
<updated>2024-03-25T05:58:14.046Z</updated>
<content type="html"><![CDATA[<h1 id="网络编程实战"><a href="#网络编程实战" class="headerlink" title="网络编程实战"></a>网络编程实战</h1><h2 id="第四讲"><a href="#第四讲" class="headerlink" title="第四讲"></a>第四讲</h2><p>调用 connect 函数将激发 TCP 的三次握手过程,而且仅在连接建立成功或出错时才返回。其中出错返回可能有以下几种情况:</p><ol><li>三次握手无法建立,客户端发出的 SYN 包没有任何响应,于是返回 TIMEOUT 错误。这种情况比较常见的原因是对应的服务端 IP 写错。</li><li>客户端收到了 RST(复位)回答,这时候客户端会立即返回 CONNECTION REFUSED 错误。这种情况比较常见于客户端发送连接请求时的请求端口写错,因为 RST 是 TCP 在发生错误时发送的一种 TCP 分节。关闭时也会产出<code>RST</code>报文与<code>SO_LINGER</code>选项有关<br>产生 RST 的三个条件是:<br>目的地为某端口的 SYN 到达,然而该端口上没有正在监听的服务器(如前所述);<br>TCP 想取消一个已有连接;<br>TCP 接收到一个根本不存在的连接上的分节。</li><li>客户发出的 SYN 包在网络上引起了”destination unreachable”,即目的不可达的错误。这种情况比较常见的原因是客户端和服务器端路由不通。</li></ol><p>为什么tcp建立连接需要三次握手解释如下<br>tcp连接的双方要确保各自的<strong>收发消息</strong>的能力都是正常的。 客户端第一次发送握手消息到服务端, 服务端接收到握手消息后把<code>ack</code>和自己的<code>syn</code>一同发送给客户端,这是第二次握手, 当客户端接收到服务端发送来的第二次握手消息后,客户端可以确认“服务端的收发能力OK,客户端的收发能力OK”,但是服务端只能确认 “客户端的发送OK,服务端的接收OK” , 所以还需要第三次握手,客户端收到服务端的第二次握手消息后,发起第三次握手消息,服务端收到客户端发送的第三次握手消息后,就能够确定“服务端的发送OK,客户端的接收OK”, 至此,客户端和服务端都能够确认自己和对方的收发能力OK,,<code>tcp</code>连接建立完成。</p><h2 id="第五讲"><a href="#第五讲" class="headerlink" title="第五讲"></a>第五讲</h2><p>阻塞式套接字最终发送返回的实际写入字节数和请求字节数是相等的即</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> ret = write(sockfd , <span class="type">void</span>* buffer , <span class="type">int</span> len);</span><br></pre></td></tr></table></figure><p>当<code>sockfd</code>是<strong>阻塞状态</strong>时,只有将buffer中len字节数据放入输出缓冲区后write函数才返回。</p><p>既然缓冲区如此重要,我们可不可以把缓冲区搞得大大的,这样不就可以提高应用程序的吞吐量了么?你可以想一想这个方法可行吗?另外你可以自己总结一下,一段数据流从应用程序发送端,一直到应用程序接收端,总共经过了多少次拷贝?<br>无限大肯定是不行的,这要从为什么使用缓存这个角度考虑。内核协议栈不确定用户一次要发多少数据,如果用户来一次就发一次,如果数据多还好说,如果少了,那网络I/O很频繁,而真正发送出去的数据也不多,所以为了减少网络I/O使用了缓存的策略。但为啥不呢无限大呢,网卡一次发出去的数据报它是有一个最大长度的,所以你不管累积再多数据最后还是要分片发送的,这样一来缓冲区太大也没什么意义,而且数据传输也是有延时要求的,不可能总是在缓冲区里待着等数据,这样就总会有空出来的缓冲区存放新数据,所以无限大缓冲区也没意义,反而还浪费资源。<br>发送端,假设数据能一次性复制完,那么从用户态内存拷贝到内核态内存是一次(这里应该直接拷贝到发送换冲区了),传输层组TCP包是第二次拷贝,因为要加包头,而发送缓冲区的都是紧凑内存全是应用层数据,那么分装包就需要一次拷贝,第三次,一个TCP包封装为IP报文这里可能也会需要一次拷贝,毕竟这里走到协议栈的下一层了。</p><h2 id="第六讲"><a href="#第六讲" class="headerlink" title="第六讲"></a>第六讲</h2><p>实际上不存在<code>UDP</code>发送缓冲区,因为发往<code>UDP</code>发送缓冲区的包只要超过一定阈值(值很小)就可以发往对端。所以我们一般认为<code>UDP</code>是没有发送缓冲区的。</p><p><code>UDP</code> 报文的大小</p><p>主要影响 <code>UDP</code> 报文大小的三大因素:</p><ul><li><code>UDP</code> 协议规定报文长度为 16 位,所以 <code>UDP</code> 的报文长度不能超过 2^16 = 65536 字节</li><li>以太网(Ethernet)数据帧的长度,这是由以太网的物理特性决定,也叫数据链路层的 <code>MTU</code>(最大传输单元)</li><li>socket 的 <code>UDP</code> 发送缓冲区大小</li></ul><p><code>UDP </code>最大数据包长度</p><p>根据 <code>UDP</code> 协议,从 <code>UDP</code> 数据包的包头可以看出,<code>UDP</code> 的最大包长度是 2^16-1 个字节。用<code>sendto</code>函数最大能发送数据的长度为:<code>65535- IP头(20) - UDP头(8)=65507字节</code>。用<code>sendto</code>函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。</p><p>由于 <code>UDP</code> 包头占 8 个字节,而在 <code>IP</code>层进行封装后的 <code>IP</code> 包头占去 20 字节,所以这个是 <code>UDP</code> 数据包的最大理论长度是 2^16 - 1 - 8 - 20 = 65507 字节。</p><p>同时 <code>UDP</code> 发送缓冲区大小(<code>linux</code>下<code>UDP</code>发送缓冲区大小为:<code>cat /proc/sys/net/core/wmem_default</code>)相关,肯定不能超过缓冲区大小。</p><p><code>UDP</code> 理想数据包长度</p><p>每个以太网帧都有最小的大小 46 字节,最大不能超过 1500 字节。</p><p>除去链路层的首部和尾部的 18 个字节,链路层的数据区范围是 46-1500 字节,</p><p>那么链路层的数据区,即 <code>MTU</code>(最大传输单元)为 1500 字节。</p><p>事实上这个 1500 字节就是网络层 <code>IP</code> 数据报的长度限制。</p><p>因为 <code>IP</code> 数据报的首部为 20 字节,所以 <code>IP</code> 数据报的数据区长度最大为 1480 字节。而这个 1480 字节就是用来放 TCP 传来的 <code>TCP</code> 报文段或 <code>UDP</code> 传来的 <code>UDP</code> 数据报的。</p><p>除去 <code>UDP</code> 包头占 8 个 字节,那么 <code>UDP</code> 数据报的数据区最大长度为 1472 字节。</p><p><strong>结论1:局域网环境下,建议将 <code>UDP</code> 数据控制在 1472 字节以下</strong></p><p>Unix 网络编程第一卷里说:<code>ipv4</code> 协议规定 <code>ip</code> 层的最小重组缓冲区大小为 576 字节,所以将 <code>UDP</code> 数据报的数据区最大长度控制在 548 字节(576-8-20)以内。</p><p><strong>结论2:<code>Internet </code>编程时,建议将 <code>UDP</code> 数据控制在 548 字节以下</strong></p><h2 id="第十讲"><a href="#第十讲" class="headerlink" title="第十讲"></a>第十讲</h2><p>TIME_WAIT的作用: 1. 确保主动断开方的最后一个ACK成功发到对方 2. 确保残留的TCP包自然消亡</p><p>优化<code>TIME_WAIT</code>,可以通过设置套接字选项来设置调用close或shutdown关闭连接时的行为</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">setsockopt</span><span class="params">(<span class="type">int</span> sockfd, <span class="type">int</span> level, <span class="type">int</span> optname, <span class="type">const</span> <span class="type">void</span> *optval,<span class="type">socklen_t</span> optlen)</span>;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">linger</span> {</span><span class="comment">//linger 英文停留</span></span><br><span class="line"> <span class="type">int</span> l_onoff; <span class="comment">/* 0=off, nonzero=on */</span></span><br><span class="line"> <span class="type">int</span> l_linger; <span class="comment">/* linger time, POSIX specifies units as seconds */</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>设置 linger 参数有几种可能:</p><ul><li><p>如果<code>l_onoff</code>为 0,那么关闭本选项。l_linger的值被忽略,这对应了默认行为,<code>close</code> 或 <code>shutdown</code> 立即返回。如果在套接字发送缓冲区中有数据残留,系统会将试着把这些数据发送出去。</p></li><li><p>如果<code>l_onoff</code>为非 0, 且<code>l_linger</code>值也为 0,那么调用 <code>close</code> 后,会立该发送一个 <code>RST</code> 标志给对端,该 <code>TCP</code> 连接将跳过四次挥手,也就<strong>跳过了 <code>TIME_WAIT</code> 状态,直接关闭</strong>。这种关闭的方式称为“强行关闭”。 在这种情况下,排队数据不会被发送,被动关闭方也不知道对端已经彻底断开。只有当被动关闭方正阻塞在<code>recv()</code>调用上时,接受到 <code>RST</code> 时,会立刻得到一个<code>“connet reset by peer”</code>的异常。</p></li><li><p>如果<code>l_onoff</code>为非 0, 且<code>l_linger</code>的值也非 0,那么调用 <code>close</code> 后,调用 <code>close</code> 的线程就将阻塞,直到数据被发送出去,或者设置的<code>l_linger</code>计时时间到。</p> <figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">linger</span> <span class="title">so_linger</span>;</span></span><br><span class="line">so_linger.l_onoff = <span class="number">1</span>;</span><br><span class="line">so_linger.l_linger = <span class="number">1</span>;</span><br><span class="line">setsockopt(s,SOL_SOCKET,SO_LINGER, &so_linger,<span class="keyword">sizeof</span>(so_linger));</span><br></pre></td></tr></table></figure></li></ul><p>对于设置端口重用选项 SO_REUSEADDR 并不是用于解决 TIME_WAIT 状态,而是告诉内核即使是TIME_WAIT状态的套接字,也可以将它继续使用作为新的套接字使用</p><h2 id="第十一讲"><a href="#第十一讲" class="headerlink" title="第十一讲"></a>第十一讲</h2><p>close 函数具体是如何关闭两个方向的数据流呢?<br>在输入方向,系统内核会将该套接字设置为<strong>不可读</strong>,任何读操作都会返回异常。<br>在输出方向,系统内核尝试将发送缓冲区的数据发送给对端,并最后向对端发送一个 FIN 报文,接下来如果再对该套接字进行写操作会返回异常。<br>如果对端没有检测到套接字已关闭,还继续发送报文,就会收到一个 RST 报文</p><p>由于close将输入设置为不可读,当服务端要做耗时任务时,由于客户端调用close()导致输入方向不可读,此时服务端运算完成返回tcp报文,但是客户端socket不可读,故内核协议栈回复RST报文</p><p>关于signal函数:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">sighandler_t</span> <span class="title function_">signal</span><span class="params">(<span class="type">int</span> signum, <span class="type">sighandler_t</span> handler)</span>;</span><br></pre></td></tr></table></figure><ul><li>如果处理方式设置为 <code>SIG_IGN</code>,则信号被忽略。 </li><li>如果处理方式设置为 <code>SIG_DFL</code>,则与信号相关的默认操作(参考 signal(7))发生。</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> SIG_DFL ((__sighandler_t) 0) <span class="comment">/* Default action. */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SIG_IGN ((__sighandler_t) 1) <span class="comment">/* Ignore signal. */</span></span></span><br></pre></td></tr></table></figure><blockquote><p>你可以看到在今天的服务器端程序中,直接调用exit(0)完成了 FIN 报文的发送,这是为什么呢?为什么不调用 close 函数或 shutdown 函数呢?</p></blockquote><p>因为在调用exit之后进程会退出,而进程相关的所有的资源,文件,内存,信号等内核分配的资源都会被释放,在<code>linux</code>中,一切皆文件,本身socket就是一种文件类型,内核会为每一个打开的文件创建<code>file</code>结构并维护指向改结构的引用计数,每一个进程结构中都会维护本进程打开的文件数组,数组下标就是<code>fd</code>,内容就指向上面的<code>file</code>结构,<code>close</code>本身就可以用来操作所有的文件,做的事就是,删除本进程打开的文件数组中指定的<code>fd</code>项,并把指向的<code>file</code>结构中的引用计数减一,等引用计数为 0 的时候,就会调用内部包含的文件操作<code>close</code>,针对于<code>socket</code>,它内部的实现就是调用<code>shutdown</code>,只是参数是关闭读写端,从而比较粗暴的关闭连接。</p><h2 id="第十二讲"><a href="#第十二讲" class="headerlink" title="第十二讲"></a>第十二讲</h2><p>socket设置保活选项</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">Socket::setKeepAlive</span><span class="params">(<span class="type">bool</span> on)</span> <span class="type">const</span> {</span><br><span class="line"> <span class="type">int</span> optval = on ? <span class="number">1</span> : <span class="number">0</span>;</span><br><span class="line"> ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE,</span><br><span class="line"> &optval, static_cast<<span class="type">socklen_t</span>>(<span class="keyword">sizeof</span> optval));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。</p><p>上述的可定义变量,分别被称为保活时间、保活时间间隔和保活探测次数。在 <code>Linux</code> 系统中,这些变量分别对应 <code>sysctl</code> 变量<code>net.ipv4.tcp_keepalive_time</code>、<code>net.ipv4.tcp_keepalive_intvl</code>、 <code>net.ipv4.tcp_keepalve_probes</code>,默认设置是 7200 秒(2 小时)、75 秒和 9 次探测。</p><p>由于TCP自身的<code>KeepAlive</code>机制所需的时间太长,对很多对时延要求敏感的系统中,这个时间间隔是不可接受的。所以通常自实现心跳机制</p><h2 id="第十三讲"><a href="#第十三讲" class="headerlink" title="第十三讲"></a>第十三讲</h2><p>Nagle算法 和 延迟ACK 的组合:</p><p>客户端分两次将一个请求发送出去,由于请求的第一部分的报文未被确认,Nagle 算法开始起作用;同时延时 ACK 在服务器端起作用,假设延时时间为 200ms,服务器等待 200ms 后,对请求的第一部分进行确认;接下来客户端收到了确认后,Nagle 算法解除请求第二部分的阻止,让第二部分得以发送出去,服务器端在收到之后,进行处理应答,同时将第二部分的确认捎带发送出去。</p><p><img src="/%5Cimg%5CSnipaste_2023-12-18_22-51-37.png"></p><p>Nagle 算法和延时确认组合在一起,增大了处理时延,实际上,两个优化彼此在阻止对方。从上面的例子可以看到,在有些情况下 Nagle 算法并不适用, 比如对时延敏感的应用</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> on = <span class="number">1</span>; <span class="comment">//关闭 Nagle 算法</span></span><br><span class="line">setsockopt(sock , IPPROTO_TCP , TCP_NODELAY , (vodi*)&on , <span class="keyword">sizeof</span>(on));</span><br></pre></td></tr></table></figure><p>值得注意的是,除非我们对此有十足的把握,否则不要轻易改变默认的 TCP Nagle 算法。因为在现代操作系统中,针对 Nagle 算法和延时 ACK 的优化已经非常成熟了,有可能在禁用 Nagle 算法之后,性能问题反而更加严重。</p><h2 id="第十五讲"><a href="#第十五讲" class="headerlink" title="第十五讲"></a>第十五讲</h2><p>重用套接字选项,通过给套接字配置可重用属性,告诉操作系统内核,这样的 TCP 连接完全可以复用 TIME_WAIT 状态的连接</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> on = <span class="number">1</span>;</span><br><span class="line">setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, <span class="keyword">sizeof</span>(on));</span><br></pre></td></tr></table></figure><p><code>SO_REUSEADDR</code> 套接字选项还有一个作用,那就是本机服务器如果有多个地址(ip地址),可以在不同地址上使用相同的端口提供服务。</p><p>要在创建socket和bind之间设置 <code>SO_REUSEADDR</code> 套接字选项 因为<code>SO_REUSEADDR</code> 是针对新建立的连接才起作用,对已建立的连接设置是无效的。</p><h2 id="第十七讲"><a href="#第十七讲" class="headerlink" title="第十七讲"></a>第十七讲</h2><p><img src="/.%5Cimg%5CSnipaste_2023-12-27_23-10-09.png"></p><ul><li>网络中断造成的对端无 FIN 包</li></ul><p>很多原因都会造成网络中断,在这种情况下,TCP 程序并不能及时感知到异常信息。除非网络中的其他设备,如路由器发出一条 ICMP 报文,说明目的网络或主机不可达,这个时候通过 read 或 write 调用就会返回 Unreachable 的错误。</p><p>大多数时候并不是如此,在没有 ICMP 报文的情况下,TCP 程序并不能理解感应到连接异常。如果程序是阻塞在 read 调用上,那么很不幸,程序无法从异常中恢复。</p><p>如果程序先调用了 write 操作发送了一段数据流,接下来阻塞在 read 调用上,结果会非常不同。Linux 系统的 TCP 协议栈会不断尝试将发送缓冲区的数据发送出去,大概在重传 12 次、合计时间约为 9 分钟之后,协议栈会标识该连接异常,这时,阻塞的 read 调用会返回一条 TIMEOUT 的错误信息。如果此时程序还执着地往这条连接写数据,写操作会立即失败,返回一个 SIGPIPE 信号给应用程序。</p><ul><li>系统崩溃造成的对端无 FIN 包</li></ul><p>当系统突然崩溃,如断电时,网络连接上来不及发出任何东西。这里和通过系统调用杀死应用程序非常不同的是,没有任何 FIN 包被发送出来。</p><p>在没有 ICMP 报文的情况下,TCP 程序只能通过 read 和 write 调用得到网络连接异常的信息,超时错误是一个常见的结果。</p><p>系统在崩溃之后又重启,当重传的 TCP 分组到达重启后的系统,由于系统中没有该 TCP 分组对应的连接数据,系统会返回一个 RST 重置分节,TCP 程序通过 read 或 write 调用可以分别对 RST 进行错误处理。</p><p>如果是阻塞的 read 调用,会立即返回一个错误,错误信息为连接重置(Connection Reset)。</p><p>如果是一次 write 操作,也会立即失败,应用程序会被返回一个 SIGPIPE 信号。</p><ul><li>对端有FIN包发出</li></ul><p>对端如果有 FIN 包发出,可能的场景是对端调用了 close 或 shutdown 显式地关闭了连接,也可能是对端应用程序崩溃,操作系统内核代为清理所发出的。<strong>从应用程序角度上看,无法区分是哪种情形</strong>。</p><h2 id="第十八讲"><a href="#第十八讲" class="headerlink" title="第十八讲"></a>第十八讲</h2><p>当服务器完全崩溃或网络故障,如果采用阻塞读,将无法感知到套接字异常,将会一直阻塞,可以为<code>read</code>设置超时,果超过了一段时间就认为连接已经不存在</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">timeval</span> <span class="title">tv</span>;</span></span><br><span class="line">tv.tv_sec = <span class="number">5</span>;</span><br><span class="line">tv.tv_usec = <span class="number">0</span>;</span><br><span class="line">setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, (<span class="type">const</span> <span class="type">char</span> *) &tv, <span class="keyword">sizeof</span> tv);</span><br><span class="line"><span class="type">int</span> nBytes = recv(connfd, buffer, <span class="keyword">sizeof</span>(buffer), <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">if</span> (nBytes == <span class="number">-1</span>) </span><br><span class="line"> <span class="keyword">if</span> (errno == EAGAIN || errno == EWOULDBLOCK) { ... }<span class="comment">//执行超时处理,如断开连接</span></span><br></pre></td></tr></table></figure><h2 id="第十九讲"><a href="#第十九讲" class="headerlink" title="第十九讲"></a>第十九讲</h2><p>一个进程无论是正常退出(exit 或者 main 函数返回),还是非正常退出(比如,收到 SIGKILL 信号关闭,就是我们常常干的 kill -9),所有该进程打开的描述符都会被系统关闭,这也导致 TCP 描述符对应的连接上发出一个 FIN 包。</p><h2 id="第二十讲"><a href="#第二十讲" class="headerlink" title="第二十讲"></a>第二十讲</h2><p>我们可以使用 fgets 方法等待标准输入,但是一旦这样做,就没有办法在套接字有数据的时候读出数据;我们也可以使用 read 方法等待套接字有数据返回,但是这样做,也没有办法在标准输入有数据的情况下,读入数据并发送给对方。I/O 多路复用的设计初衷就是解决这样的场景。我们可以把标准输入、套接字等都看做 I/O 的一路,多路复用的意思,就是在任何一路 I/O 有“事件”发生的情况下,通知应用程序去处理相应的 I/O 事件,这样我们的程序就变成了“多面手”,在同一时刻仿佛可以处理多个 I/O 事件。select所支持的文件描述符上线只有1024个</p><blockquote><p>你认为 select 函数里一定需要传入描述字基数这个值么?</p></blockquote><p>需要设置。<code>int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);</code> 函数select检测相当于遍历三个 fd_set,需要知道数组的上限</p><h2 id="第二十一讲"><a href="#第二十一讲" class="headerlink" title="第二十一讲"></a>第二十一讲</h2><p>poll 突破了select对文件描述符的限制</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">poll</span><span class="params">(<span class="keyword">struct</span> pollfd *fds, <span class="type">unsigned</span> <span class="type">long</span> nfds, <span class="type">int</span> timeout)</span>; </span><br></pre></td></tr></table></figure><ul><li>pollfd 数组:<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">pollfd</span> {</span></span><br><span class="line"> <span class="type">int</span> fd; <span class="comment">/* file descriptor */</span></span><br><span class="line"> <span class="type">short</span> events; <span class="comment">/* events to look for POLLIN POOLOUT*/</span></span><br><span class="line"> <span class="type">short</span> revents; <span class="comment">/* events returned */</span></span><br><span class="line"> };</span><br><span class="line">其中对应的事件:</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLIN 0x0001 <span class="comment">/* any readable data available */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLPRI 0x0002 <span class="comment">/* OOB/Urgent readable data */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLRDNORM 0x0040 <span class="comment">/* non-OOB/URG data available */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLRDBAND 0x0080 <span class="comment">/* OOB/Urgent readable data */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLOUT 0x0004 <span class="comment">/* file descriptor is writeable */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLWRNORM POLLOUT <span class="comment">/* no write type differentiation */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLWRBAND 0x0100 <span class="comment">/* OOB/Urgent data can be written */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLERR 0x0008 <span class="comment">/* 一些错误发送 */</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLHUP 0x0010 <span class="comment">/* 描述符挂起*/</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> POLLNVAL 0x0020 <span class="comment">/* 请求的事件无效*/</span></span></span><br></pre></td></tr></table></figure></li></ul><p>如果我们<strong>不想对某个 pollfd 结构进行事件检测,</strong>可以把它对应的 pollfd 结构的 fd 成员设置成一个负值。这样,poll 函数将忽略这样的 events 事件,检测完成以后,所对应的“returned events”的成员值也将设置为 0。在 poll 函数里,我们可以控制 pollfd 结构的数组大小,这意味着我们可以突破原来 select 函数最大描述符的限制,在这种情况下,应用程序调用者需要分配 pollfd 数组并通知 poll 函数该数组的大小。</p><h2 id="第二十二讲"><a href="#第二十二讲" class="headerlink" title="第二十二讲"></a>第二十二讲</h2><p>read / write:</p><p>非阻塞读操作:如果套接字对应的接收缓冲区没有数据可读,在非阻塞情况下 read 调用会立即返回,一般返回 EWOULDBLOCK 或 EAGAIN 出错信息</p><p>非阻塞写操作:在非阻塞 I/O 的情况下,如果套接字的发送缓冲区已达到了极限,不能容纳更多的字节,那么操作系统内核会<strong>尽最大可能</strong>从应用程序拷贝数据到发送缓冲区中,并立即从 write 等函数调用中返回已拷贝的字节数</p><p>accept:</p><p>当 accept 和 I/O 多路复用 select、poll 等一起配合使用时,如果在监听套接字上触发事件,说明有连接建立完成,此时调用 accept 肯定可以返回已连接套接字。但是总有例外</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//客户端</span></span><br><span class="line">connect();<span class="comment">//在收到服务端回的ack时返回,进入establish状态</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">linger</span> <span class="title">ling</span>;</span></span><br><span class="line">ling.l_onoff = <span class="number">1</span>; </span><br><span class="line">ling.l_linger = <span class="number">0</span>;</span><br><span class="line">setsockopt(socket_fd, SOL_SOCKET, SO_LINGER, &ling, <span class="keyword">sizeof</span>(ling));</span><br><span class="line">close(socket_fd);<span class="comment">//此时服务端没有调用accept,就受到了RST报文</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//服务端</span></span><br><span class="line"><span class="keyword">if</span> (FD_ISSET(listen_fd, &readset)) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"listening socket readable\n"</span>);</span><br><span class="line"> sleep(<span class="number">5</span>);<span class="comment">//时延</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_storage</span> <span class="title">ss</span>;</span></span><br><span class="line"> <span class="type">socklen_t</span> slen = <span class="keyword">sizeof</span>(ss);</span><br><span class="line"> <span class="type">int</span> fd = accept(listen_fd, (<span class="keyword">struct</span> sockaddr *) &ss, &slen);</span><br></pre></td></tr></table></figure><p>这里的休眠时间非常关键,这样,在监听套接字上有可读事件发生时,并没有马上调用 accept。由于客户端发生了 RST 分节,该连接被接收端内核从自己的已完成队列中删除了,此时再调用 accept,由于没有已完成连接(假设没有其他已完成连接),accept 一直阻塞,更为严重的是,该线程再也没有机会对其他 I/O 事件进行分发,相当于该服务器无法对其他 I/O 进行服务。如果我们将监听套接字设为非阻塞,上述的情形就不会再发生。只不过对于 accept 的返回值,需要正确地处理各种看似异常的错误,例如忽略 EWOULDBLOCK、EAGAIN 等。</p><p>connect:</p><p>非阻塞调用时会立即返回 EINPROGRESS 错误,连接后会返回 EISCONN 错误</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(<span class="number">1</span>) {</span><br><span class="line"><span class="keyword">if</span>(connect() == EISCONN)</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="第二十三讲"><a href="#第二十三讲" class="headerlink" title="第二十三讲"></a>第二十三讲</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">epoll_event</span> {</span></span><br><span class="line"> <span class="type">uint32_t</span> events; <span class="comment">/* Epoll events */</span></span><br><span class="line"> <span class="type">epoll_data_t</span> data; <span class="comment">/* User data variable */</span></span><br><span class="line"> };</span><br><span class="line">* EPOLLIN:表示对应的文件描述字可以读;</span><br><span class="line"></span><br><span class="line">* EPOLLOUT:表示对应的文件描述字可以写;</span><br><span class="line"></span><br><span class="line">* EPOLLRDHUP:表示套接字的一端已经关闭,或者半关闭;</span><br><span class="line"></span><br><span class="line">* EPOLLHUP:表示对应的文件描述字被挂起;</span><br><span class="line"></span><br><span class="line">* EPOLLET:设置为 edge-triggered,默认为 level-triggered。</span><br></pre></td></tr></table></figure><p>水平触发(level-trggered)</p><ul><li>只要文件描述符关联的读内核缓冲区非空,有数据可以读取,就一直发出可读信号进行通知,</li><li>当文件描述符关联的内核写缓冲区不满,有空间可以写入,就一直发出可写信号进行通知</li></ul><p>边缘触发(edge-triggered)</p><ul><li>当文件描述符关联的读内核缓冲区由空转化为非空的时候,则发出可读信号进行通知,</li><li>当文件描述符关联的内核写缓冲区由满转化为不满的时候,则发出可写信号进行通知</li></ul><p>在linux下,如果用边缘触发同时注册了读和写,当读触发的时候,内核向用户返回fd的时候同时会检查fd是否符合可写的条件(有空间容纳待写入的数据),如果满足可写的条件,同时会加上EPOLLOUT标记。</p><h2 id="第三十讲"><a href="#第三十讲" class="headerlink" title="第三十讲"></a>第三十讲</h2><p>无论是阻塞 I/O,还是阻塞 I/O,和基于非阻塞 I/O 的多路复用都是<strong>同步调用技术。为什么这么说呢?因为同步调用、异步调用的说法,是对于获取数据的过程而言的,前面几种最后获取数据的 read 操作调用,都是同步的,在 read 调用时,内核将数据从内核空间拷贝到应用程序空间,这个过程是在 read 函数中同步进行的,如果内核实现的拷贝效率很差,read 调用就会在这个同步过程中消耗比较长的时间</strong></p><p>而真正的异步调用则不用担心这个问题,我们接下来就来介绍第四种 I/O 技术,当我们发起 io_uring之后,就立即返回,内核自动将数据从内核空间拷贝到应用程序空间,这个拷贝过程是异步的,内核自动完成的,和前面的同步操作不一样,应用程序并不需要主动发起拷贝动作。</p><p><a href="https://juejin.cn/post/6844903879688060942">https://juejin.cn/post/6844903879688060942</a> 重置报文</p>]]></content>
<summary type="html"><h1 id="网络编程实战"><a href="#网络编程实战" class="headerlink" title="网络编程实战"></a>网络编程实战</h1><h2 id="第四讲"><a href="#第四讲" class="headerlink" title="第四讲</summary>
<category term="网络编程" scheme="http://example.com/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>share_ptr和weak_ptr</title>
<link href="http://example.com/2024/01/15/3.1share_ptr%E4%B8%8Eweak_ptr/"/>
<id>http://example.com/2024/01/15/3.1share_ptr%E4%B8%8Eweak_ptr/</id>
<published>2024-01-14T16:00:00.000Z</published>
<updated>2024-03-15T13:28:25.037Z</updated>
<content type="html"><![CDATA[<h1 id="share-ptr和weak-ptr"><a href="#share-ptr和weak-ptr" class="headerlink" title="share_ptr和weak_ptr"></a>share_ptr和weak_ptr</h1><h2 id="share-ptr"><a href="#share-ptr" class="headerlink" title="share_ptr"></a>share_ptr</h2><h3 id="内存模型"><a href="#内存模型" class="headerlink" title="内存模型"></a>内存模型</h3><p>shared_ptr 内部包含两个指针,一个指向对象,另一个指向控制块(control block),控制块中包含一个引用计数(reference count), 一个弱计数(weak count)和其它一些数据。</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/share_ptr%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.png"></p><p>当执行:<code>share_ptr<int> p1(new int(1));share_ptr<int> p2 = p1</code> 时对应的share_ptr结构中指针将指向同一个对象以及控制块</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/share_ptr%E8%B5%8B%E5%80%BC.png"></p><p>std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析构的时候,内存才会被释放。</p><p>share_ptr的线程安全问题: </p><blockquote><p><strong>引用计数是线程安全的</strong>,引用计数使用了原子类型,指向对象数据,如果发生修改,将不是线程安全的,若要数据安全,要在对象数据访问上增加锁机制保证对象的数据安全</p></blockquote><h3 id="基本用法,常用函数"><a href="#基本用法,常用函数" class="headerlink" title="基本用法,常用函数"></a>基本用法,常用函数</h3><p>只能通过复制构造或复制赋值其值给另一 <code>shared_ptr</code> ,将对象所有权与另一 <code>shared_ptr</code> 共享。用另一 <code>shared_ptr</code> 所占有的底层指针创建新的 <code>shared_ptr</code> 导致未定义行为。</p><p><code>std::shared_ptr</code> 可以用于不完整类型T 。然而,参数为裸指针的构造函数( template<class Y> shared_ptr(Y * ) )和 template<class Y> void reset(Y*) 成员函数只可以用指向完整类型的指针调用(注意 <a href="https://www.apiref.com/cpp-zh/cpp/memory/unique_ptr.html">std::unique_ptr</a> 可以从指向不完整类型的裸指针构造)。</p><ul><li>通过构造函数,<code>make_shared()</code> ,<code>reset()</code>方法来初始化<code>shared_ptr</code></li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::shared_ptr<<span class="type">int</span>> <span class="title">p1</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>(<span class="number">1</span>))</span></span>;</span><br><span class="line">std::shared_ptr<<span class="type">int</span>> p2 = std::<span class="built_in">make_shared</span><<span class="type">int</span>>(<span class="number">100</span>);</span><br><span class="line">std::shared_ptr<<span class="type">int</span>> p3;</span><br><span class="line">p3.<span class="built_in">reset</span>(<span class="keyword">new</span> <span class="built_in">int</span>(<span class="number">1</span>));</span><br></pre></td></tr></table></figure><p>不能将一个原始指针直接赋值给一个智能指针<code>std::shared_ptr<int> p = new int(10)</code>这种写法将无法编译,shared_ptr不能通过“直接将原始这种赋值”来初始化,需要通过构造函数或辅助方法来初始化,因为<code>template< class Y > explicit shared_ptr( Y* ptr );</code>参数类型是指针的构造函数被explict修饰(指定构造函数或转换函数 (C++11 起)或推导指引显式,<strong>即它不能用于隐式转换和复制初始化</strong></p><ul><li>智能指针可以通过重载的bool类型操作符来判断</li></ul><p>提供了<code>explicit operator bool() const noexcept;</code>检查 <code>*this</code> 是否存储非空指针,即是否有 <code>get() != nullptr</code></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">report</span><span class="params">(std::shared_ptr<<span class="type">int</span>> ptr)</span> </span>{</span><br><span class="line"><span class="keyword">if</span>(ptr) {</span><br><span class="line">std::cout << *ptr <<std::endl;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line">std::cout << <span class="string">"*ptr is not a valid pointer \n"</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>std::shared_ptr<T>::use_count,返回引用计数</li></ul><p>返回管理当前对象的不同 <code>shared_ptr</code> 实例(包含 this )数量。若无管理对象,则返回 0。<strong>多线程环境下, use_count 返回的值是近似的</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">fun</span><span class="params">(std::shared_ptr<<span class="type">int</span>> sp)</span> </span>{</span><br><span class="line"> std::cout << <span class="string">"fun: sp.use_count() == "</span> << sp.<span class="built_in">use_count</span>() << <span class="string">'\n'</span>; </span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="keyword">auto</span> sp1 = std::<span class="built_in">make_shared</span><<span class="type">int</span>>(<span class="number">5</span>);</span><br><span class="line"> std::cout << <span class="string">"sp1.use_count() == "</span> << sp1.<span class="built_in">use_count</span>() << <span class="string">'\n'</span>; </span><br><span class="line"> <span class="comment">//sp1.use_count() == 1</span></span><br><span class="line"> <span class="built_in">fun</span>(sp1);</span><br><span class="line"> <span class="comment">//fun: sp.use_count() == 2</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>reset方法</li></ul><p>对于无参数reset,<code>void reset() noexcept</code>释放被管理对象的所有权,若存在。调用后, *this 不管理对象。等价于 shared_ptr().swap( *this)</p><p>对于存在一个参数的reset,<code>template< class Y >void reset( Y* ptr );</code>以 <code>ptr</code> 所指向的对象替换被管理对象,以 delete 表达式为删除器。合法的 delete 表达式必须可用,即 delete ptr 必须为良式,拥有良好定义行为且不抛任何异常。等价于 shared_ptr<T>(ptr).swap(*this); 。</p><ul><li>获取原始指针get方法</li></ul><p>当需要获取原始指针时,可以通过get方法来返回原始指针</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">std::shared_ptr<<span class="type">int</span>> <span class="title">ptr</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>{<span class="number">1</span>})</span></span>;</span><br><span class="line"><span class="type">int</span> *p = ptr.<span class="built_in">get</span>();<span class="comment">//获取原始指针</span></span><br></pre></td></tr></table></figure><p>谨慎使用<code>p.get()</code>的返回值:</p><blockquote><p>不要保存ptr.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的 。保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针</p><p>不要delete ptr.get()的返回值 ,会导致对一块内存delete两次的错误</p></blockquote><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="type">int</span> * p = <span class="keyword">new</span> <span class="built_in">int</span>(<span class="number">10</span>);</span><br><span class="line"> <span class="function">std::shared_ptr<<span class="type">int</span>> <span class="title">p1</span><span class="params">(p)</span></span>;</span><br><span class="line"> <span class="function">std::shared_ptr<<span class="type">int</span>> <span class="title">p2</span><span class="params">(p1.get())</span></span>;<span class="comment">//错误将会double free</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>如上代码是错误的</strong>,<code>p1.get()</code>返回指针类型,故调用<code>template< class Y > explicit shared_ptr( Y* ptr )</code>,对于此构造函数,只是构造 <code>shared_ptr</code> ,管理 <code>ptr</code> 所指向的对象,此构造过程并不会产生共享对象,<code>shared_ptr( const shared_ptr& r ) noexcept;</code>和<code>operator=</code>会产生共享对象</p><p><strong>不要将this指针作为shared_ptr返回出来</strong>,原理同上</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">shared_ptr<A> <span class="title">GetSelf</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">shared_ptr</span><A>(<span class="keyword">this</span>); <span class="comment">// 不要这么做</span></span><br><span class="line">}</span><br><span class="line">~<span class="built_in">A</span>(){ cout << <span class="string">"Destructor A"</span> << endl; }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>{</span><br><span class="line"><span class="function">shared_ptr<A> <span class="title">sp1</span><span class="params">(<span class="keyword">new</span> A)</span></span>;</span><br><span class="line">shared_ptr<A> sp2 = sp1-><span class="built_in">GetSelf</span>();</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>正确返回this的shared_ptr的做法是:让目标类通过std::enable_shared_from_this类,然后使用基类的 成员函数shared_from_this()来返回this的shared_ptr</strong></p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>: <span class="keyword">public</span> std::enable_shared_from_this<A></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function">shared_ptr<A> <span class="title">GetSelf</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> <span class="built_in">shared_from_this</span>(); </span><br><span class="line">}</span><br><span class="line">~<span class="built_in">A</span>(){cout << <span class="string">"Destructor A"</span> << endl;}</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="function">shared_ptr<A> <span class="title">sp1</span><span class="params">(<span class="keyword">new</span> A)</span></span>;</span><br><span class="line">shared_ptr<A> sp2 = sp1-><span class="built_in">GetSelf</span>(); <span class="comment">// ok</span></span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>不要在函数实参中创建 <code>share_ptr</code></li></ul><p>如:<code> function(std::shared_ptr<int> (new int(10)) , g() )</code>因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也 可能从左到右,所以,可能的过程是先<code>new int</code>,然后调用<code>g()</code>,如果恰好<code>g()</code>发生异常,而<code>shared_ptr</code>还没有创建, 则<code>int</code>内存泄漏了,正确的写法应该是先创建智能指针</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">shared_ptr<<span class="type">int</span>> <span class="title">p</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>)</span></span>;</span><br><span class="line"><span class="built_in">function</span>(p, <span class="built_in">g</span>());</span><br></pre></td></tr></table></figure><ul><li>避免循环引用</li></ul><p>循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不回减为0,导致两个指针都不会被析构,产生内存泄漏,解决的办法是把A和B任何一个成员变量改为weak_ptr</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> std::shared_ptr<B> bptr;</span><br><span class="line"> ~<span class="built_in">A</span>()</span><br><span class="line"> {</span><br><span class="line"> cout << <span class="string">"A is deleted"</span> << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> std::shared_ptr<A> aptr;</span><br><span class="line"> ~<span class="built_in">B</span>()</span><br><span class="line"> {</span><br><span class="line"> cout << <span class="string">"B is deleted"</span> << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> {</span><br><span class="line"> <span class="function">std::shared_ptr<A> <span class="title">ap</span><span class="params">(<span class="keyword">new</span> A)</span></span>;</span><br><span class="line"> <span class="function">std::shared_ptr<B> <span class="title">bp</span><span class="params">(<span class="keyword">new</span> B)</span></span>;</span><br><span class="line"> ap->bptr = bp;</span><br><span class="line"> bp->aptr = ap;</span><br><span class="line"> }</span><br><span class="line"> cout << <span class="string">"main leave"</span> << endl; <span class="comment">// 循环引用导致ap bp退出了作用域都没有析构</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="weak-ptr"><a href="#weak-ptr" class="headerlink" title="weak_ptr"></a>weak_ptr</h2><p><code>weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 </code>Shared_ptr<code> 管理的对象. 进行该对象的内存管理的是那个强引用的</code>shared_ptr<code>,</code> weak_ptr`只是提供了对管理对象的一个访问手段。</p><h3 id="基本用法:"><a href="#基本用法:" class="headerlink" title="基本用法:"></a>基本用法:</h3><ul><li>通过use_count()方法获取当前观察资源的引用计数</li></ul><p>返回共享被管理对象所有权的 <code>shared_ptr</code> 实例数量,或 0 ,若被管理对象已被删除,即 <code>*this</code> 为空。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="function">shared_ptr<<span class="type">int</span>> <span class="title">sp</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>(<span class="number">10</span>))</span></span>;</span><br><span class="line"> weak_ptr<<span class="type">int</span>> wp = sp;</span><br><span class="line"> cout << wp.<span class="built_in">use_count</span>() <<endl; <span class="comment">//1</span></span><br><span class="line"> shared_ptr<<span class="type">int</span>> sp1 = sp;</span><br><span class="line"> cout <<wp.<span class="built_in">use_count</span>() << endl; <span class="comment">//2</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>通过expired()方法判断所观察资源是否已经释放</li></ul><p><code>bool expired() const noexcept;</code>等价于 <code>use_count() == 0</code> 。可能仍未对被管理对象调用析构函数,但此对象的析构已经临近(或可能已发生),若被管理对象已被删除则为 true ,否则为 false 。若被管理对象在线程间共享,则此函数内在地不可靠,通常 false 结果可能在能用之前就变得过时。 true 结果可靠。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">shared_ptr<<span class="type">int</span>> <span class="title">sp</span><span class="params">(<span class="keyword">new</span> <span class="type">int</span>(<span class="number">10</span>))</span></span>;</span><br><span class="line"><span class="function">weak_ptr<<span class="type">int</span>> <span class="title">wp</span><span class="params">(sp)</span></span>;</span><br><span class="line"><span class="keyword">if</span>(wp.<span class="built_in">expired</span>())</span><br><span class="line">cout << <span class="string">"weak_ptr无效,资源已释放"</span>;</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">cout << <span class="string">"weak_ptr有效"</span>;</span><br></pre></td></tr></table></figure><ul><li>通过lock方法获取监视的shared_ptr</li></ul><p><code>std::shared_ptr<T> lock() const noexcept</code>,创建新的 <code>std::shared_ptr</code> 对象,它共享被管理对象的所有权。若无被管理对象,即 <code>*this</code> 为空,则返回亦为<code>nullptr</code>的 <code>shared_ptr</code>,等效地返回 <code>expired() ? shared_ptr<T>() : shared_ptr<T>(*this)</code> ,<strong>原子地执行</strong></p><p>在多线程环境下:</p><p>线程一:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">std::weak_ptr<<span class="type">int</span>> gw;</span><br><span class="line">gw = sp;</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">f</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">auto</span> spt = gw.<span class="built_in">lock</span>();</span><br><span class="line"> <span class="keyword">if</span> ( gw.<span class="built_in">expired</span>() ) {</span><br><span class="line"> cout << <span class="string">" gw Invalid , resource released"</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> cout << <span class="string">" gw Vaild , *spt = "</span> << *spt;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>线程二:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">std::shared_ptr<<span class="type">int</span>> sp = std::<span class="built_in">make_shared</span><<span class="type">int</span>>(<span class="number">10</span>);</span><br></pre></td></tr></table></figure><p>在线程二中资源有可能释放,为了保证线程一不出错,要使用weak_ptr观察,要<strong>先上锁后检查</strong>,根据加锁情况分类</p><ul><li>weak_ptr解决循环引用</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> std::weak_ptr<B> bptr; <span class="comment">// 修改为weak_ptr</span></span><br><span class="line"> ~<span class="built_in">A</span>()</span><br><span class="line"> { cout << <span class="string">"A is deleted"</span> << endl;}</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> std::shared_ptr<A> aptr;</span><br><span class="line"> ~<span class="built_in">B</span>()</span><br><span class="line"> { cout << <span class="string">"B is deleted"</span> << endl; }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> {</span><br><span class="line"> <span class="function">std::shared_ptr<A> <span class="title">ap</span><span class="params">(<span class="keyword">new</span> A)</span></span>;</span><br><span class="line"> <span class="function">std::shared_ptr<B> <span class="title">bp</span><span class="params">(<span class="keyword">new</span> B)</span></span>;</span><br><span class="line"> ap->bptr = bp;</span><br><span class="line"> bp->aptr = ap;</span><br><span class="line"> }</span><br><span class="line"> cout << <span class="string">"main leave"</span> << endl;</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样在对B的成员赋值时,即执行<code>bp->aptr=ap;</code>时,由于<code>aptr</code>是<code>weak_ptr</code>,它并不会增加引用计数,所以ap的引用计数仍然会是1,在离开作用域之后,ap的引用计数为减为0,A指针会被析构,析构后其内部的<code>bptr</code>的引用计数会被减为1,然后在离开作用域后<code>bp</code>引用计数又从1减为0,B对象也被析构,不会发生内存泄漏</p><p><a href="https://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html">https://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html</a>多线程环境下share_ptr写时加锁原因</p>]]></content>
<summary type="html"><h1 id="share-ptr和weak-ptr"><a href="#share-ptr和weak-ptr" class="headerlink" title="share_ptr和weak_ptr"></a>share_ptr和weak_ptr</h1><h2 id="s</summary>
<category term="从0到1" scheme="http://example.com/tags/%E4%BB%8E0%E5%88%B01/"/>
</entry>
<entry>
<title>dpdk收发数据</title>
<link href="http://example.com/2023/12/30/2.%E6%94%B6%E5%8F%91%E6%95%B0%E6%8D%AE/"/>
<id>http://example.com/2023/12/30/2.%E6%94%B6%E5%8F%91%E6%95%B0%E6%8D%AE/</id>
<published>2023-12-29T16:00:00.000Z</published>
<updated>2024-03-15T13:27:58.098Z</updated>
<content type="html"><![CDATA[<h1 id="使用dpdk收发数据"><a href="#使用dpdk收发数据" class="headerlink" title="使用dpdk收发数据"></a>使用dpdk收发数据</h1><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><rte_eal.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><rte_ethdev.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><rte_mbuf.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><arpa/inet.h></span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> NUM_MBUFS (4096-1)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> BURST_SIZE32</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ENABLE_SEND 1</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> ENABLE_SEND</span></span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">uint32_t</span> gSrcIp;</span><br><span class="line"><span class="type">static</span> <span class="type">uint32_t</span> gDstIp;</span><br><span class="line"><span class="type">static</span> <span class="type">uint8_t</span> gSrcMac[RTE_ETHER_ADDR_LEN];</span><br><span class="line"><span class="type">static</span> <span class="type">uint8_t</span> gDstMac[RTE_ETHER_ADDR_LEN];</span><br><span class="line"><span class="type">static</span> <span class="type">uint16_t</span> gSrcPort;</span><br><span class="line"><span class="type">static</span> <span class="type">uint16_t</span> gDstPort;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> gDpdkPortId = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> <span class="class"><span class="keyword">struct</span> <span class="title">rte_eth_conf</span> <span class="title">port_conf_default</span> =</span> {</span><br><span class="line">.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">void</span> <span class="title function_">ng_init_port</span><span class="params">(<span class="keyword">struct</span> rte_mempool *mbuf_pool)</span> {</span><br><span class="line"></span><br><span class="line"><span class="type">uint16_t</span> nb_sys_ports= rte_eth_dev_count_avail(); <span class="comment">//</span></span><br><span class="line"><span class="keyword">if</span> (nb_sys_ports == <span class="number">0</span>) {</span><br><span class="line">rte_exit(EXIT_FAILURE, <span class="string">"No Supported eth found\n"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_eth_dev_info</span> <span class="title">dev_info</span>;</span></span><br><span class="line">rte_eth_dev_info_get(gDpdkPortId, &dev_info); <span class="comment">//</span></span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> num_rx_queues = <span class="number">1</span>;</span><br><span class="line"><span class="type">const</span> <span class="type">int</span> num_tx_queues = <span class="number">1</span>;</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_eth_conf</span> <span class="title">port_conf</span> =</span> port_conf_default;</span><br><span class="line">rte_eth_dev_configure(gDpdkPortId, num_rx_queues, num_tx_queues, &port_conf);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (rte_eth_rx_queue_setup(gDpdkPortId, <span class="number">0</span> , <span class="number">128</span>, <span class="comment">//队列和mbuf_pool有关联</span></span><br><span class="line">rte_eth_dev_socket_id(gDpdkPortId),<span class="literal">NULL</span>, mbuf_pool) < <span class="number">0</span>) {</span><br><span class="line"></span><br><span class="line">rte_exit(EXIT_FAILURE, <span class="string">"Could not setup RX queue\n"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> ENABLE_SEND</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_eth_txconf</span> <span class="title">txq_conf</span> =</span> dev_info.default_txconf;</span><br><span class="line">txq_conf.offloads = port_conf.rxmode.offloads;</span><br><span class="line"><span class="keyword">if</span> (rte_eth_tx_queue_setup(gDpdkPortId, <span class="number">0</span> , <span class="number">1024</span>, <span class="comment">//gDpdkPortId号网卡的第0号队列,最大容纳128个包</span></span><br><span class="line">rte_eth_dev_socket_id(gDpdkPortId) , &txq_conf) < <span class="number">0</span>) {</span><br><span class="line"> rte_exit(EXIT_FAILURE, <span class="string">"Could not setup TX queue\n"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (rte_eth_dev_start(gDpdkPortId) < <span class="number">0</span> ) {</span><br><span class="line">rte_exit(EXIT_FAILURE, <span class="string">"Could not start\n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//rte_eth_promiscuous_enable(gDpdkPortId);//混杂模式</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="type">int</span> <span class="title function_">ng_encode_udp_pkt</span><span class="params">(<span class="type">uint8_t</span>* msg , <span class="type">unsigned</span> <span class="type">char</span>* data , <span class="type">uint16_t</span> total_len)</span> {</span><br><span class="line"><span class="comment">//在msg所指的位置用data替代</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//eth</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_ether_hdr</span>* <span class="title">eth</span> =</span> (<span class="keyword">struct</span> rte_ether_hdr*)msg;</span><br><span class="line">rte_memcpy(&eth->s_addr , &gSrcMac , RTE_ETHER_ADDR_LEN);</span><br><span class="line">rte_memcpy(&eth->d_addr, &gDstMac, RTE_ETHER_ADDR_LEN);</span><br><span class="line">eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);</span><br><span class="line"><span class="comment">//ip</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_ipv4_hdr</span>* <span class="title">ip</span> =</span> (<span class="keyword">struct</span> rte_ipv4_hdr*)(msg + <span class="keyword">sizeof</span>(<span class="keyword">struct</span> rte_ether_hdr));</span><br><span class="line">ip->version_ihl = <span class="number">0x45</span>;</span><br><span class="line">ip->type_of_service = <span class="number">0</span>;</span><br><span class="line">ip->total_length = htonl(total_len - <span class="keyword">sizeof</span>(<span class="keyword">struct</span> rte_ether_hdr));</span><br><span class="line">ip->packet_id = <span class="number">0</span>;</span><br><span class="line">ip->fragment_offset = <span class="number">0</span>;</span><br><span class="line">ip->time_to_live = <span class="number">64</span>;</span><br><span class="line">ip->next_proto_id = IPPROTO_UDP;</span><br><span class="line">ip->src_addr = gSrcIp;</span><br><span class="line">ip->dst_addr = gDstIp;</span><br><span class="line">ip->hdr_checksum = <span class="number">0</span>;</span><br><span class="line">ip->hdr_checksum = rte_ipv4_cksum(ip);</span><br><span class="line"><span class="comment">//udp</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_udp_hdr</span>* <span class="title">udp</span> =</span> (<span class="keyword">struct</span> rte_udp_hdr*)(ip + <span class="number">1</span>);</span><br><span class="line">udp->src_port = gSrcPort;</span><br><span class="line">udp->dst_port = gDstPort;</span><br><span class="line">udp->dgram_len = htons(total_len - <span class="keyword">sizeof</span>(<span class="keyword">struct</span> rte_ether_hdr) - <span class="keyword">sizeof</span>(<span class="keyword">struct</span> rte_ipv4_hdr));</span><br><span class="line"><span class="type">uint16_t</span> udp_len = total_len - <span class="keyword">sizeof</span>(<span class="keyword">struct</span> rte_ether_hdr) - <span class="keyword">sizeof</span>(<span class="keyword">struct</span> rte_ipv4_hdr);</span><br><span class="line">rte_memcpy(udp+<span class="number">1</span> , data , udp_len);</span><br><span class="line">udp->dgram_cksum= <span class="number">0</span>;</span><br><span class="line">udp->dgram_cksum = rte_ipv4_udptcp_cksum(ip ,udp);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="keyword">struct</span> rte_mbuf* <span class="title function_">ng_send</span><span class="params">(<span class="keyword">struct</span> rte_mempool* mbuf_pool, <span class="type">unsigned</span> <span class="type">char</span>* msg , <span class="type">uint16_t</span> length)</span> {</span><br><span class="line"><span class="comment">//从mbufpool 中获取 mbuf</span></span><br><span class="line"><span class="type">const</span> <span class="type">unsigned</span> total_len = <span class="number">14</span> + <span class="number">20</span> + <span class="number">8</span> + length;<span class="comment">//eth + ip + udp + 应用数据</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_mbuf</span>* <span class="title">mbuf</span> =</span> rte_pktmbuf_alloc(mbuf_pool);</span><br><span class="line">mbuf->pkt_len = total_len;</span><br><span class="line">mbuf->data_len = total_len;</span><br><span class="line"></span><br><span class="line"><span class="type">uint8_t</span>* pktdata = rte_pktmbuf_mtod(mbuf, <span class="type">uint8_t</span>*);</span><br><span class="line">ng_encode_udp_pkt(pktdata , msg ,length);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> mbuf;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> {</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (rte_eal_init(argc, argv) < <span class="number">0</span>) {</span><br><span class="line">rte_exit(EXIT_FAILURE, <span class="string">"Error with EAL init\n"</span>);</span><br><span class="line">}</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"init success!! \n"</span>);</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_mempool</span> *<span class="title">mbuf_pool</span> =</span> rte_pktmbuf_pool_create(<span class="string">"mbuf pool"</span>, NUM_MBUFS,</span><br><span class="line"><span class="number">0</span>, <span class="number">0</span>, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());</span><br><span class="line"><span class="keyword">if</span> (mbuf_pool == <span class="literal">NULL</span>) {</span><br><span class="line">rte_exit(EXIT_FAILURE, <span class="string">"Could not create mbuf pool\n"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">rte_eth_macaddr_get(gDpdkPortId , (<span class="keyword">struct</span> rte_ether_addr*)gSrcMac);</span><br><span class="line"></span><br><span class="line">ng_init_port(mbuf_pool);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"mbuf_pool init success!! \n"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span>(<span class="number">1</span>) {</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_mbuf</span>* <span class="title">mbufs</span>[<span class="title">BUFSIZ</span>];</span></span><br><span class="line"><span class="type">unsigned</span> num_recvd = rte_eth_rx_burst(gDpdkPortId , <span class="number">0</span> , mbufs , BUFSIZ);</span><br><span class="line"><span class="keyword">if</span>(num_recvd > BUFSIZ) {</span><br><span class="line">rte_exit(EXIT_FAILURE , <span class="string">"Error receiving from eth \n"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">unsigned</span> <span class="type">int</span> i = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span>(i=<span class="number">0</span> ; i<num_recvd ; ++i) {</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_ether_hdr</span>* <span class="title">ehdr</span> =</span> rte_pktmbuf_mtod(mbufs[i] , <span class="keyword">struct</span> rte_ether_hdr*);</span><br><span class="line"><span class="keyword">if</span>(ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {</span><br><span class="line">rte_pktmbuf_free(mbufs[i]);</span><br><span class="line"><span class="keyword">continue</span>;</span><br><span class="line">}</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_ipv4_hdr</span>* <span class="title">iphdr</span> =</span> rte_pktmbuf_mtod_offset(mbufs[i] , <span class="keyword">struct</span> rte_ipv4_hdr* , <span class="keyword">sizeof</span>(<span class="keyword">struct</span> rte_ether_hdr));</span><br><span class="line"><span class="keyword">if</span>(iphdr->next_proto_id == IPPROTO_UDP) {</span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_udp_hdr</span>* <span class="title">udphdr</span> =</span> (<span class="keyword">struct</span> rte_udp_hdr*)((<span class="type">char</span>*)iphdr + <span class="keyword">sizeof</span>(<span class="keyword">struct</span> rte_ipv4_hdr));</span><br><span class="line"><span class="meta">#<span class="keyword">if</span> ENABLE_SEND</span></span><br><span class="line">rte_memcpy(&gDstMac , &ehdr->d_addr , RTE_ETHER_ADDR_LEN);</span><br><span class="line">gSrcIp = iphdr->dst_addr;</span><br><span class="line">gDstIp = iphdr->src_addr;</span><br><span class="line">gSrcPort = udphdr->dst_port;</span><br><span class="line">gDstPort = udphdr->src_port;</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">uint16_t</span> length = htons(udphdr->dgram_len);</span><br><span class="line">*((<span class="type">char</span>*)(udphdr + length)) = <span class="string">'\0'</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">in_addr</span> <span class="title">addr</span>;</span></span><br><span class="line">addr.s_addr = iphdr->src_addr;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"src:%s %d "</span> , inet_ntoa(addr) , ntohs(udphdr->src_port));</span><br><span class="line"></span><br><span class="line">addr.s_addr = iphdr->dst_addr;</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"dst:%s %d length=%d:%s\n"</span> , inet_ntoa(addr) , ntohs(udphdr->dst_port) , </span><br><span class="line">length , (<span class="type">char</span>*)((<span class="type">char</span>*)udphdr + <span class="keyword">sizeof</span>(udphdr)));</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">if</span> ENABLE_SEND</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">rte_mbuf</span>* <span class="title">txbuf</span> =</span> ng_send(mbuf_pool , (<span class="type">unsigned</span> <span class="type">char</span>*)(udphdr + <span class="number">1</span>) , length);</span><br><span class="line">rte_eth_tx_burst(gDpdkPortId , <span class="number">0</span> , &txbuf , <span class="number">1</span>);</span><br><span class="line">rte_pktmbuf_free(txbuf);</span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line">rte_pktmbuf_free(mbufs[i]);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="使用API"><a href="#使用API" class="headerlink" title="使用API"></a>使用API</h2><p>在DPDK中,rte前缀代表<strong>Runtime Environment</strong>,即运行环境。DPDK的主要对外函数接口都以rte_作为前缀,抽象化函数接口是典型软件设计思路,可以帮助DPDK运行在多个操作系统上。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">rte_eal_init</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> ** argv )</span>;</span><br></pre></td></tr></table></figure><p>此函数用于初始化环境抽象层,函数成功时返回值大于或等于0,所有参数 argv[x] (x < 返回值) 可能已经被这个函数修改,失败返回 -1,并设置<code>rte_error</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">rte_exit</span><span class="params">(<span class="type">int</span> exit_code , <span class="type">const</span> <span class="type">char</span>* format)</span>;</span><br></pre></td></tr></table></figure><p>此函数用于立即终止应用程序,打印错误信息并将退出码返回shell</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> rte_mempool* <span class="title function_">rte_pktmbuf_pool_create</span><span class="params">(<span class="type">const</span> <span class="type">char</span>* name , <span class="type">unsigned</span> cache_size , <span class="type">uint16_t</span> priv_size </span></span><br><span class="line"><span class="params"> , <span class="type">uint16_t</span> data_room_size , <span class="type">int</span> socket_id)</span>;</span><br></pre></td></tr></table></figure><ul><li>name:内存池的名称。</li><li>n:内存池中的元素数量。</li><li>cache_size:每个 CPU 缓存的大小,单位为元素数目,如果为 0 则表示禁用缓存。</li><li>priv_size:每个元素的私有数据空间大小。可以使用 0 表示没有私有数据。</li><li>data_room_size:每个元素中存储数据的空间大小。</li><li>socket_id:内存池所在的 NUMA 节点编号。</li></ul><p>该函数返回一个指向新创建的 mempool 的指针。这个 mempool 可以通过 rte_pktmbuf_alloc 和 rte_pktmbuf_free 命令进行分配和释放。</p><p>dpdk中一个进程确定一个内存池,将发送数据和接收数据都放在内存池中</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">unsigned</span> <span class="type">int</span> <span class="title function_">rte_socket_id</span><span class="params">(<span class="type">void</span>)</span>;</span><br></pre></td></tr></table></figure><p>返回当前正常运行此函数网卡对应的socketid</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">uint16_t</span> <span class="title function_">rte_eth_dev_count_avail</span><span class="params">(<span class="type">void</span>)</span>;</span><br></pre></td></tr></table></figure><p>获取可用于dpdk的网卡的个数</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">rte_eth_dev_configure</span><span class="params">(<span class="type">uint16_t</span> port_id , <span class="type">uint16_t</span> nb_rx_queue,</span></span><br><span class="line"><span class="params"><span class="type">uint16_t</span> nb_tx_queue, <span class="type">const</span> <span class="keyword">struct</span> rte_eth_conf* eth_conf )</span></span><br></pre></td></tr></table></figure><p>此函数用于配置网卡设备,此函数必须被调用在任何与网卡api有关的接口之前</p><ul><li>port_id:要配置的网卡对应的id</li><li>nb_rx_queue:接收队列的个数</li><li>nb_tx_queue:发送队列的个数</li><li>eth_conf:指向要对网卡进行的配置操作的结构体</li></ul><p>成功返回 0 失败返回 < 0 的错误号</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">rte_eth_rx_queue_setup</span><span class="params">(<span class="type">uint16_t</span> port_id , <span class="type">uint16_t</span> rx_queue_id,</span></span><br><span class="line"><span class="params"> <span class="type">uint16_t</span> nb_rx_desc , <span class="type">unsigned</span> <span class="type">int</span> socket_id,</span></span><br><span class="line"><span class="params"> <span class="type">const</span> <span class="keyword">struct</span> rte_eth_rxconf *rx_conf , <span class="keyword">struct</span> rte_mempool *mb_pool )</span></span><br></pre></td></tr></table></figure><p>该函数从与 socket_id 关联的内存区域中分配一个连续的内存块,用于存放 nb_rx_desc 个接收描述符,并使用从内存池 mb_pool 中分配的网络缓冲区初始化每个接收描述符。</p><ul><li>port_id:以太网设备的端口标识符。每个物理网卡都有一个唯一的端口 ID,用于在 DPDK 中标识该设备。</li><li>rx_queue_id:要设置的接收队列的索引。每个物理网卡可以有多个接收队列,用于并行处理接收到的数据包。该值必须在之前调用 rte_eth_dev_configure() 函数时指定的范围 [0, nb_rx_queue - 1] 内。</li><li>nb_rx_desc:要为接收环分配的接收描述符的数量。接收描述符用于描述数据包在接收环中的位置和状态。该值通常由应用程序的性能需求和物理网卡的特性决定。</li><li>socket_id:在 NUMA 系统中,指定用于分配接收描述符的内存所在的 NUMA 节点 ID。如果没有 NUMA 限制,则可以设置为 SOCKET_ID_ANY。</li><li>rx_conf:指向要用于接收队列的配置数据的指针。如果设置为 NULL,则使用默认的接收配置</li><li>mb_pool:指向内存池的指针,用于分配 rte_mbuf 网络内存缓冲区,以填充接收环的每个描述符。rte_mbuf 是 DPDK 中用于存储数据包数据的结构。</li></ul><p>返回 0 表示成功</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">uint16_t</span> <span class="title function_">rte_eth_rx_burst</span><span class="params">(<span class="type">uint16_t</span> port_id,<span class="type">uint16_t</span> queue_id,</span></span><br><span class="line"><span class="params"><span class="keyword">struct</span> rte_mbuf** rx_pkts , <span class="type">const</span> <span class="type">uint16_t</span> nb_pkts )</span></span><br></pre></td></tr></table></figure><p>从对应的网卡对应的队列中收数据包,被检索到的数据包被存放到<code>rx_pkts</code>数组中指向的<code>rte_mbuf</code>结构体中,此函数是非阻塞调用,这意味着它会立即返回,即使没有可用的输入数据包。如果没有可用的输入数据包,它将返回 0。</p><ul><li><code>port_id</code>:以太网设备端口标识符。</li><li><code>queue_id</code>:要检索数据包的接收队列索引。该值必须在之前提供给 <code>rte_eth_dev_configure()</code> 的范围 <code>[0, nb_rx_queue - 1]</code> 内。</li><li><code>rx_pkts</code>:用于存储检索到的数据包的 <code>rte_mbuf</code> 结构指针数组。</li><li><code>nb_pkts</code>:要检索的最大数据包数。</li></ul><p>返回实际检索到的数据包数,指示 <code>rx_pkts</code> 数组中填充了多少 <code>rte_mbuf</code> 指针。<strong>无错误通知</strong>:该函数不提供错误通知以避免开销。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">#define rte_pktmbuf_mtod(m, t) rte_pktmbuf_mtod_offset(m, t, 0)</span><br><span class="line">#define rte_pktmbuf_mtod_offset(m, t, o)\</span><br><span class="line">((t)((char *)(m)->buf_addr + (m)->data_off + (o)))</span><br></pre></td></tr></table></figure><p><code>rte_pktmbuf_mtod()</code> 是 DPDK 中用于将 <code>rte_mbuf</code> 结构指针转换为特定类型指针的接口。全称是 <strong>rte_mbuf to data pointer</strong>,即“<code>rte_mbuf</code> 结构指针到数据指针”。</p>]]></content>
<summary type="html"><h1 id="使用dpdk收发数据"><a href="#使用dpdk收发数据" class="headerlink" title="使用dpdk收发数据"></a>使用dpdk收发数据</h1><figure class="highlight c"><table><tr><t</summary>
<category term="dpdk" scheme="http://example.com/tags/dpdk/"/>
</entry>
<entry>
<title>dpdk环境搭建</title>
<link href="http://example.com/2023/12/29/1.%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/"/>
<id>http://example.com/2023/12/29/1.%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/</id>
<published>2023-12-28T16:00:00.000Z</published>
<updated>2024-03-15T13:26:13.713Z</updated>
<content type="html"><![CDATA[<h1 id="dpdk环境搭建"><a href="#dpdk环境搭建" class="headerlink" title="dpdk环境搭建"></a>dpdk环境搭建</h1><ol><li><p>将虚拟机对应虚拟网卡驱动设置为 <code> vmxnet3</code><br>因为vmxnet3是支持多队列的网卡,多队列网卡意味着,可以出发cpu的多个中断</p></li><li><p>修改网卡名:</p></li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">vi /etc/default/grub</span><br><span class="line">将GRUB_CMDLINE_LINUX修改为:</span><br><span class="line">GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0" </span><br></pre></td></tr></table></figure><p>重建grub配置文件<br>执行命令:<code>sudo grub-mkconfig -o /boot//grub/g</code>rub.cfg</p><p>重启后网卡名就修改好了 如 eth0</p><ol start="3"><li>网络配置文件: /etc/network/interfaces,使对应网卡采用dhcp自动获取ip</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">The loopback network interface</span></span><br><span class="line">auto lo</span><br><span class="line">iface lo inet loopback</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">The primary network interface</span></span><br><span class="line">auto eth0</span><br><span class="line">iface etho inet dhcp</span><br><span class="line"></span><br><span class="line">auto eth1</span><br><span class="line">iface eth1 inet dhcp</span><br><span class="line"></span><br><span class="line">auto eth2</span><br><span class="line">iface eth2 inet dhcp</span><br></pre></td></tr></table></figure><ol start="4"><li><p>添加网卡,但是在ifconfig时,发现无eth0,执行 <code>ifconfig eth0 up</code>此时 eth0 被开启,但是无 <code>ip </code>被分配,执行 <code>dhclient eth0</code> 为<code>eth0</code> 分配ip</p></li><li><p>在启动参数内添加巨页信息:</p></li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">default_hugepages=1G hugepagesz=2M hugepages=1024 isolcpus=0-2</span><br></pre></td></tr></table></figure><p>重建grub配置文件<br>执行命令:<code>sudo grub-mkconfig -o /boot//grub/grub.cfg</code></p><ol start="6"><li>查看是否支持多队列网卡</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">cat /proc/interrupts存在如下内容</span><br><span class="line"></span><br><span class="line"> 56: 114 1015 PCI-MSI 1114112-edge 0000:02:04.0</span><br><span class="line"> 57: 2 258 PCI-MSI 1572864-edge eth0-rxtx-0</span><br><span class="line"> 58: 5 0 PCI-MSI 1572865-edge eth0-rxtx-1</span><br><span class="line"> 59: 0 0 PCI-MSI 1572866-edge eth0-event-2</span><br><span class="line"> 60: 5 305 PCI-MSI 5767168-edge eth1-rxtx-0</span><br><span class="line"> 61: 8 9 PCI-MSI 5767169-edge eth1-rxtx-1</span><br><span class="line"> 62: 0 0 PCI-MSI 5767170-edge eth1-event-2</span><br></pre></td></tr></table></figure><p>可以看到eth0 eth1都是多队列网卡,并且有两个都对应两个中断(因为虚拟机我只分配了2个核心)</p><ol start="7"><li>下载dpdk代码,并设置环境变量<br><code>export RTE_SDK=/home/satellite/share/dpdk-stable-19.08.2</code><br><code> export RTE_TARGET=x86_64-native-linux-gcc</code><br>执行:<code>/usertools/dpdk-setup.sh</code></li></ol><ul><li><p>dhclient命令来自英文词组DHCP client的缩写,其功能是动态获取或释放IP地址。使用dhclient命令前,需要将网卡模式设置成DHCP自动获取,否则静态模式的网卡不会主动向服务器获取如IP地址等网卡信息。</p></li><li><p><code>/etc/default/grub</code> 是ubuntu开机引导文件,修改<code> GRUB_CMDLINE_LINUX</code> 就是修改了内核的启动参数</p><p>grup是GNU GRand Unified Bootloader的缩写。GRand代表”Generalized”和”Randomized”。在GRUB的背景中,”GRand”意味着它是一个通用且灵活的引导加载程序,可以适用于各种操作系统和硬件平台。</p></li></ul>]]></content>
<summary type="html"><h1 id="dpdk环境搭建"><a href="#dpdk环境搭建" class="headerlink" title="dpdk环境搭建"></a>dpdk环境搭建</h1><ol>
<li><p>将虚拟机对应虚拟网卡驱动设置为 <code> vmxnet3</code></summary>
<category term="dpdk" scheme="http://example.com/tags/dpdk/"/>
</entry>
<entry>
<title>网络协议栈</title>
<link href="http://example.com/2023/10/12/6.3%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E6%A0%88/"/>
<id>http://example.com/2023/10/12/6.3%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E6%A0%88/</id>
<published>2023-10-11T16:00:00.000Z</published>
<updated>2023-12-04T14:17:31.024Z</updated>
<content type="html"><![CDATA[<h1 id="网络协议栈"><a href="#网络协议栈" class="headerlink" title="网络协议栈"></a>网络协议栈</h1><p><img src="/img/Snipaste_2023-08-17_00-08-10.png"></p><p>客户端调用connect函数发起连接,此时服务端调用完成listen函数,进入监听状态,这个过程可以类比客户和饭馆,客服先在饭店门口取用餐号(饭店进行记录),饭店通知客户进来用餐,客户进饭店用餐。在协议栈的实现中,当第一个syn发来时,将对应节点加入服务端的syn半连接队列,当最后一个ack发来时,从对应半连接状态中找到对应节点加入服务端的accept全连接队列。这个节点被称为tcb控制块</p><p>accept函数要做的是:从accept全连接队列A中取出一个节点并为其分配一个文件描述符fd</p><blockquote><p>三次握手客户端在哪个函数?<br>connect</p><p>三次握手服务端在哪个函数?<br>并不是listen,listen只是将连接保存在半连接队列,此时三次握手没有完成。所以,服务器并没有发生在那个函数中而是在listen和accept之间完成的,服务端是被动完成的握手</p><p>如何在半连接队列中找到对应的tcb(tcp control block)节点?<br>tcp的每个过程都伴随着tcp头部字段,其中包含了五元组(sip,sport,dip,dport,proto),根据五元组是否相同筛选出位于半连接队列的的tcb控制块,将其移到全连接队列中</p><p>send返回一个正数,是不是发送成功了?<br>send函数仅仅只是将buffer中的数据从用户空间拷贝到内核空间,与内核空间是否发送没有关系</p><p>listenfd可不可以收发数据?<br>listenfd可以收发数据,位于三次握手阶段,用于接收syn与发ack</p></blockquote><h2 id="四次挥手问题"><a href="#四次挥手问题" class="headerlink" title="四次挥手问题"></a>四次挥手问题</h2><p>四次挥手不区分客户端还是服务端,<strong>只区分主动还是被动</strong>。主动方先发送FIN,被动方ACK确认,被动方发送FIN,主动发发送ACK确认</p><p><img src="/img/Snipaste_2023-08-17_09-21-47.png"></p><p>主动方与被动方同时处于established状态,主动方调用close函数进入fin_wait1状态,被动方调用recv函数返回0,并返回确认包使主动方进入fin_wait_2状态,被动方进入close_wait状态,被动方调用close函数发送设置fin标志的数据包进入last_ack状态,主动方收到带有fin标注的数据包计入time_wait状态,主动方回复确认包结束被动方last_ack状态</p><p>close()函数就是将FIN写入包结构中,如send() 后立即close() ,那么最后一个数据包中就包含了FIN位,对应的客户端会从FIN_WAIT_1状态直接尽然TIME_WAIT状态,close()仅仅只是关闭fd并发送FIN,会使tcb走向回收,但此时还没有被回收</p><blockquote><p>如果出现大量close_wait如何解决?<br>说明客户端调用close()后服务端recv() == 0 由于业务原因没有及时调用close(),可以将业务和网络分离开,使close()及时被调用</p><p>有没有可能双方同时调用close()?</p><p>为什么会有time_wait状态?<br>TIME_WAIT状态存在的原因有两点:<br>可靠地终止TCP连接。<br>保证让迟来的TCP报文段有足够的时间被识别并丢弃。<br>第一个原因很好理解。假设用于确认服务器结束丢失,那么服务器将重发结束报文段。因此客户端需要停留在某个状态以处理重复收到的结束报文段(即向服务器发送确认报文段)。否则,客户端将以复位报文段来回应服务器,服务器则认为这是一个错误,因为它期望的是一个像TCP报文段7那样的确认报文段。 在Linux系统上,一个TCP端口不能被同时打开多次(两次及以 上)。当一个TCP连接处于TIME_WAIT状态时,我们将无法立即使用该连接占用着的端口来建立一个新连接。反过来思考,如果不存在 TIME_WAIT状态,则应用程序能够立即建立一个和刚关闭的连接相似的连接(这里说的相似,是指它们具有相同的IP地址和端口号)。这 个新的、和原来相似的连接被称为原来的连接的化身(incarnation)。 新的化身可能接收到属于原来的连接的、携带应用程序数据的TCP报文段(迟到的报文段),这显然是不应该发生的。这就是TIME_WAIT状态存在的第二个原因。</p></blockquote><h2 id="tcp状态转移"><a href="#tcp状态转移" class="headerlink" title="tcp状态转移"></a>tcp状态转移</h2><p><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-10-12_11-53-44.png"></p><p>CLOSED是一个假想的起始点,并不是一个实际的状态。</p><p>服务器通过listen系统调用进入LISTEN状态,被动等待客户端连接,因此执行的是所谓的被动打开。服务器一旦监听到某个连接请求(收到同步报文段),就将该连接放入内核<strong>等待队列</strong>中, 并向客户端发送带SYN标志的确认报文段。此时该连接处于SYN_RCVD状态。如果服务器成功地接收到客户端发送回的确认报文段,则该连接转移到ESTABLISHED状态。ESTABLISHED状态是连接双方能够进行双向数据传输的状态。</p><p>当客户端主动关闭连接时(通过close或shutdown系统调用向服务器发送结束报文段),服务器通过返回确认报文段使连接进入 CLOSE_WAIT状态。这个状态的含义很明确:等待服务器应用程序关闭连接。通常,服务器检测到客户端关闭连接后,也会立即给客户端 发送一个结束报文段来关闭连接。这将使连接转移到LAST_ACK状态,以等待客户端对结束报文段的最后一次确认。一旦确认完成,连接就彻底关闭了。</p><p>客户端通过connect系统调用主动与服务器建立连接。 connect系统调用首先给服务器发送一个同步报文段,使连接转移到 SYN_SENT状态。如果connect连接的目标端口不存在(未被任何进程监听),或者该端口仍被处于TIME_WAIT状态的连接所占用(见后文),则服务 器将给客户端发送一个复位报文段,connect调用失败。如果目标端口存在,但connect在超时时间内未收到服务器的确 认报文段,则connect调用失败。</p><p>connect调用失败将使连接立即返回到初始的CLOSED状态。如果客户端成功收到服务器的同步报文段和确认,则connect调用成功返 回,连接转移至ESTABLISHED状态。</p><p>当客户端执行主动关闭时,它将向服务器发送一个结束报文段, 同时连接进入FIN_WAIT_1状态。若此时客户端收到服务器专门用于确 认目的的确认报文段,则连接转移至 FIN_WAIT_2状态。当客户端处于FIN_WAIT_2状态时,服务器处于 CLOSE_WAIT状态,这一对状态是可能发生半关闭的状态。此时如果服务器也关闭连接(发送结束报文段),则客户端将给予确认并进入 TIME_WAIT状态。</p><p>状态转移图还给出了客户端从FIN_WAIT_1状态直接进入TIME_WAIT状态的一条线路(不经过FIN_WAIT_2状态),前提是处于FIN_WAIT_1 状态的服务器直接收到带确认信息的结束报文段(而不是先收到确认报文段,再收到结束报文段)。</p><h2 id="数据的传输"><a href="#数据的传输" class="headerlink" title="数据的传输"></a>数据的传输</h2><p>对于数据的发送,有三种情况</p><ol><li>一次send()</li><li>连续send()</li><li>send()发送大文件</li></ol><p><code>send(fd , buf , len , 0)</code>仅仅是将数据拷贝到fd所指向的tcb控制块的发送缓冲区中,对于发送是由协议栈自己决定什么时候发送,所以对于连续send()可能出现,两次send()的数据结合为一个数据包发送,也有可能连续send()导致一次send中的数据被分在两个数据包中,这就是所谓的<strong>分包和粘包</strong></p><p>基于tcp的流式数据即数据顺序不会改变,就有了两种解决分包和粘包的方法</p><ol><li>在应用层协议头中指定包长度</li><li>为每一个包加上分隔符</li></ol><blockquote><p>网线断了,连接会消失吗?<br>网卡会重启,协议栈会清空,再次连接网线时需要重新建立tcp连接</p><p>服务端进程崩溃,客户端会发生什么?<br>TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP四次挥手的过程。</p><p>服务端主机宕机后,客户端会发生什么?</p><p>当服务端的主机突然断电了,这种情况就是属于服务端主机宕机了。<br>当服务端的主机发生了宕机,是没办法和客户端进行四次挥手的,所以在服务端主机发生宕机的那一时刻,客户端是没办法立刻感知到服务端主机宕机了,只能在后续的数据交互中来感知服务端的连接已经不存在了。<br>因此,我们要分两种情况来讨论:</p><ul><li>服务端主机宕机后,客户端会发送数据;</li><li>服务端主机宕机后,客户端一直不会发送数据;</li></ul><p><strong>服务端主机宕机后,如果客户端会发送数据</strong></p><p>在服务端主机宕机后,客户端发送了数据报文,由于得不到响应,在等待一定时长后,客户端就会触发超时重传机制,重传未得到响应的数据报文。</p><p>当重传次数达到达到一定阈值后,内核就会判定出该 TCP 连接有问题,然后通过 Socket 接口告诉应用程序该 TCP 连接出问题了,于是客户端的 TCP 连接就会断开。</p><p><strong>服务端主机宕机后,如果客户端一直不发数据</strong></p><p>在服务端主机发送宕机后,如果客户端一直不发送数据,那么还得看是否开启了 TCP keepalive 机制 (TCP 保活机制)。</p><p>如果没有开启 TCP keepalive 机制,在服务端主机发送宕机后,如果客户端一直不发送数据,那么客户端的 TCP 连接将一直保持存在,所以我们可以得知一个点,在没有使用 TCP 保活机制,且双方不传输数据的情况下,一方的 TCP 连接处在 ESTABLISHED 状态时,并不代表另一方的 TCP 连接还一定是正常的。</p><p>而如果开启了 TCP keepalive 机制,在服务端主机发送宕机后,即使客户端一直不发送数据,在持续一段时间后,TCP 就会发送探测报文,探测服务端是否存活:</p><ul><li>如果对端是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来。</li><li>如果对端主机崩溃,或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡。</li></ul><p>所以,TCP keepalive 机制可以在双方没有数据交互的情况,通过探测报文,来确定对方的 TCP 连接是否存活。</p><p>应用程序如果想使用 TCP 保活机制,需要通过 socket 接口设置 SO_KEEPALIVE 选项才能够生效,如果没有设置,那么就无法使用 TCP 保活机制。</p></blockquote><h2 id="Nagle算法"><a href="#Nagle算法" class="headerlink" title="Nagle算法"></a>Nagle算法</h2><p><code>Nagle</code>算法主要用来预防小分组的产生。在广域网上,大量TCP小分组极有可能造成网络的拥塞。</p><p><code>Nagle</code>是针对每一个TCP连接的。它要求一个TCP连接上最多只能有一个未被确认的小分组。在改分组的确认到达之前不能发送其他小分组。TCP会搜集这些小的分组,然后在之前小分组的确认到达后将刚才搜集的小分组<strong>合并</strong>发送出去。</p><p>有时候我们必须要关闭<code>Nagle</code>算法,特别是在一些对时延要求较高的交互式操作环境中,所有的小分组必须尽快发送出去。</p><p>我们可以通过编程取消<code>Nagle</code>算法,利用<code>TCP_NODELAY</code>选项来关闭<code>Nagle</code>算法</p><p>参考:<br>《Linux高性能服务器编程》<br><a href="https://www.51cto.com/article/718024.html">https://www.51cto.com/article/718024.html</a></p>]]></content>
<summary type="html"><h1 id="网络协议栈"><a href="#网络协议栈" class="headerlink" title="网络协议栈"></a>网络协议栈</h1><p><img src="/img/Snipaste_2023-08-17_00-08-10.png"></p>
<p>客</summary>
<category term="从0到1" scheme="http://example.com/tags/%E4%BB%8E0%E5%88%B01/"/>
<category term="网络编程" scheme="http://example.com/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>网络IO</title>
<link href="http://example.com/2023/10/11/6.2%E7%BD%91%E7%BB%9Cio/"/>
<id>http://example.com/2023/10/11/6.2%E7%BD%91%E7%BB%9Cio/</id>
<published>2023-10-10T16:00:00.000Z</published>
<updated>2023-10-12T12:20:12.349Z</updated>
<content type="html"><![CDATA[<h1 id="网络IO"><a href="#网络IO" class="headerlink" title="网络IO"></a>网络IO</h1><h2 id="阻塞与非阻塞io"><a href="#阻塞与非阻塞io" class="headerlink" title="阻塞与非阻塞io"></a>阻塞与非阻塞io</h2><p>read / write 有两个职责(检测与拷贝),read检测读buffer中是否有可读数据并根据情况完成内核到用户的拷贝,write检测写buffer中是否有可写位置并根据情况完成用户到内核数据的拷贝</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-10-10_10-37-11.png"></p><p>连接的 fd 阻塞属性决定了 io 函数是否阻塞,对于阻塞IO检测到读buffer中无数据 或 写buffer中已满,会阻塞等待知道读buffer中有数据或写buffer中有位置后在完成数据的拷贝,当然这可能会出现,实际读写的数据少于想要读写的数据即:<code>ret = read(fd , buf , sz) </code>中<code>ret < sz</code>。对于非阻塞IO检测到读buffer中无数据 或 写buffer中已满时会立即返回。具体差异在:IO 函数在数据未就绪时是否立刻返回</p><h2 id="非阻塞IO处理方式"><a href="#非阻塞IO处理方式" class="headerlink" title="非阻塞IO处理方式"></a>非阻塞IO处理方式</h2><p>所有的IO函数都有一个参数fd,这意味着IO函数只能检测一条连接的就绪状态以及操作一条IO的数据</p><h3 id="连接的建立"><a href="#连接的建立" class="headerlink" title="连接的建立"></a>连接的建立</h3><p>connect:</p><p>其中connect分为两种,一种是接收客户端的连接,另一种是服务器作为客户端主动去连接其他服务器如mysql服务器</p><p>客户端非阻塞IO连接</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"> <span class="type">int</span> fd = socket(AF_INET , SOCK_STREAM , <span class="number">0</span>);</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> <span class="title">servaddr</span>;</span></span><br><span class="line"> servaddr.sin_family = AF_INET;</span><br><span class="line"> servaddr.sin_port = htons(atoi(argv[<span class="number">2</span>]));</span><br><span class="line"> inet_pton(AF_INET,argv[<span class="number">1</span>],&servaddr.sin_addr);</span><br><span class="line"> <span class="type">int</span> flag = fcntl(fd , F_GETFL);</span><br><span class="line"> fcntl(fd , F_SETFL , flag | O_NONBLOCK);</span><br><span class="line"> <span class="keyword">while</span>(<span class="number">1</span>) {</span><br><span class="line"> connect(fd, &servaddr, <span class="keyword">sizeof</span>(servaddr));</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"connect:%s errno:%d \n"</span>,strerror(errno) , errno);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line">输出:</span><br><span class="line">connect:Operation now in progress errno:<span class="number">115</span></span><br><span class="line">connect:Operation now in progress errno:<span class="number">115</span></span><br><span class="line">connect:Transport endpoint is already connected errno:<span class="number">106</span></span><br><span class="line">connect:Transport endpoint is already connected errno:<span class="number">106</span></span><br></pre></td></tr></table></figure><p>对于非阻塞IO的<code>connect</code>需要循环连接,其中<code>errno</code>由<code>EINPROGRESS(正在建立)</code>转变为<code>EISCONN(已经连接)</code></p><p>listen:</p><p>会创建半连接队列和全连接队列</p><p>accept:</p><p>对于非阻塞fd,首先<strong>检测</strong>全连接队列中没有可用的连接,会返回-1并设置<code>errno</code>为<code>EWOULDBLOCK</code></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"> <span class="type">int</span> listenfd = socket(AF_INET , SOCK_STREAM , <span class="number">0</span>);</span><br><span class="line"> <span class="type">int</span> flag = fcntl(listenfd , F_GETFL);</span><br><span class="line"> fcntl(listenfd , F_SETFL , flag | O_NONBLOCK);</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> <span class="title">addr</span>;</span></span><br><span class="line"> <span class="built_in">memset</span>(&addr , <span class="number">0</span> , <span class="keyword">sizeof</span> addr);</span><br><span class="line"> addr.sin_addr.s_addr = htonl(INADDR_ANY);</span><br><span class="line"> addr.sin_family = AF_INET;</span><br><span class="line"> addr.sin_port = htons(<span class="number">8888</span>);</span><br><span class="line"> <span class="keyword">if</span>( <span class="number">-1</span> == bind(listenfd , &addr , <span class="keyword">sizeof</span>(addr)) ) perror(<span class="string">"bind"</span>);</span><br><span class="line"> <span class="keyword">if</span>(<span class="number">-1</span> == listen(listenfd , <span class="number">20</span>)) perror(<span class="string">"listen"</span>);</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">sockaddr_in</span> <span class="title">client</span>;</span></span><br><span class="line"> <span class="type">socklen_t</span> len = <span class="keyword">sizeof</span> client;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(<span class="number">1</span>) {</span><br><span class="line"> <span class="type">int</span> cfd = accept(listenfd, &client, &len);</span><br><span class="line"> <span class="keyword">if</span>(errno == EWOULDBLOCK)</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"cfd:%d accept:%s errno:%d \n"</span>,cfd,strerror(errno) , errno);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line">输出:</span><br><span class="line">cfd:<span class="number">-1</span> accept:Resource temporarily unavailable errno:<span class="number">11</span></span><br><span class="line">cfd:<span class="number">-1</span> accept:Resource temporarily unavailable errno:<span class="number">11</span></span><br><span class="line">cfd:<span class="number">-1</span> accept:Resource temporarily unavailable errno:<span class="number">11</span></span><br></pre></td></tr></table></figure><h3 id="连接断开"><a href="#连接断开" class="headerlink" title="连接断开"></a>连接断开</h3><p>连接建立,若某一端关闭连接,而另一端仍然向它写数据,第一次写数据后会收到RST(Reset the connection)响应,此后再写数据,内核将向进程发出SIGPIPE信号,通知进程此连接已经断开。而SIGPIPE信号的默认处理是终止程序</p><p>主动断开:close shutdown</p><p>被动断开:对端读端关闭 read() = 0 对端写端关闭 write() = -1 && errno = EPIPE</p><h2 id="reactor"><a href="#reactor" class="headerlink" title="reactor"></a>reactor</h2><p>reactor将对IO的操作转化为了对事件的处理</p><p>reactor由io多路复用和非阻塞IO组成,IO多路复用负责检测IO事件,非阻塞IO用于操作IO</p><p>reactor为什么要搭配非阻塞IO?</p><blockquote><ol><li>多线程环境下, 会将一个listenfd添加到多个epoll中,这里只有一个listenfd,对应只会有一个全连接队列,但是有多个epoll从这个全连接队列中获取节点,当有多个线程同时对一个节点进行accept时,只会有一个accept成功,若listenfd是阻塞IO其他线程会阻塞在accept上等待返回,若是非阻塞会返回-1并将errno设为EWOULDBLOCK</li><li>在边缘触发下必须使用非阻塞IO,边缘触发要求每次将readbuf中的数据读完,需要借助非阻塞IO read返回 -1 时的errno来判断结束条件</li><li>当reactor使用select时,select存在一个bug,当某个socket接收缓冲区有新数据分节到达,然后select报告这个socket描述符可读,但随后协议栈检查到这个新节点检验和错误,然后丢弃这个节点,这时候调用read则无数据可读,如果socket没有被设置为非阻塞,则此read会阻塞线程</li></ol></blockquote><p>无论是C++还是Java编写的网络框架,大多数都是基于Reactor模型进行设计和开发,Reactor模型基于事件驱动,特别适合处理海量的I/O事件。</p><p>Reactor模型中定义的三种角色:</p><ul><li>Reactor:负责监听和分配事件,将I/O事件分派给对应的Handler。新的事件包含连接建立就绪、读就绪、写就绪等。</li><li>Acceptor:处理客户端新连接,并分派请求到处理器链中。</li><li>Handler:将自身与事件绑定,执行非阻塞读/写任务,完成channel的读入,完成处理业务逻辑后,负责将结果写出channel。可用资源池来管理。</li></ul><p>Reactor处理请求的流程:</p><p>读取操作:</p><ol><li>应用程序注册读就绪事件和相关联的事件处理器</li><li>事件分离器等待事件的发生</li><li>当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器</li></ol><p>写入操作类似于读取操作,只不过第一步注册的是写就绪事件。</p><h4 id="1-单Reactor单线程模型"><a href="#1-单Reactor单线程模型" class="headerlink" title="1.单Reactor单线程模型"></a>1.单Reactor单线程模型</h4><p>Reactor线程负责多路分离套接字,accept新连接,并分派请求到handler。<a href="https://cloud.tencent.com/product/crs?from_column=20065&from=20065">Redis</a>使用单Reactor单进程的模型。</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-10-11_11-54-08.png"></p><p>消息处理流程:</p><ol><li>Reactor对象通过select监控连接事件,收到事件后通过dispatch进行转发。</li><li>如果是连接建立的事件,则由acceptor接受连接,并创建handler处理后续事件。</li><li>如果不是建立连接事件,则Reactor会分发调用Handler来响应。</li><li>handler会完成read->业务处理->send的完整业务流程。</li></ol><p>单Reactor单线程模型只是在代码上进行了组件的区分,但是整体操作还是单线程,不能充分利用硬件资源。handler业务处理部分没有异步。</p><p>对于一些小容量应用场景,可以使用单Reactor单线程模型。但是对于高负载、大并发的应用场景却不合适,主要原因如下:</p><ol><li>即便Reactor线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送。</li><li>当Reactor线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重Reactor线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈。</li><li>一旦Reactor线程意外中断或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。</li></ol><p>为了解决这些问题,演进出单Reactor多线程模型。</p><h4 id="2-单Reactor多线程模型"><a href="#2-单Reactor多线程模型" class="headerlink" title="2.单Reactor多线程模型"></a>2.单Reactor多线程模型</h4><p>该模型在事件处理器(Handler)部分采用了多线程(线程池)。</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-10-11_11-54-14.png" alt="img"></p><p>消息处理流程:</p><ol><li>Reactor对象通过Select监控客户端请求事件,收到事件后通过dispatch进行分发。</li><li>如果是建立连接请求事件,则由acceptor通过accept处理连接请求,然后创建一个Handler对象处理连接完成后续的各种事件。</li><li>如果不是建立连接事件,则Reactor会分发调用连接对应的Handler来响应。</li><li>Handler只负责响应事件,不做具体业务处理,通过Read读取数据后,会分发给后面的Worker线程池进行业务处理。</li><li>Worker线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给Handler进行处理。</li><li>Handler收到响应结果后通过send将响应结果返回给Client。</li></ol><p>相对于第一种模型来说,在处理业务逻辑,也就是获取到IO的读写事件之后,交由线程池来处理,handler收到响应后通过send将响应结果返回给客户端。这样可以降低Reactor的性能开销,从而更专注的做事件分发工作了,提升整个应用的吞吐。</p><p>但是这个模型存在的问题:</p><ol><li>多线程数据共享和访问比较复杂。如果子线程完成业务处理后,把结果传递给主线程Reactor进行发送,就会涉及共享数据的互斥和保护机制。</li><li>Reactor承担所有事件的监听和响应,只在主线程中运行,可能会存在性能问题。例如并发百万客户端连接,或者服务端需要对客户端握手进行安全认证,但是认证本身非常损耗性能。</li></ol><p>为了解决性能问题,产生了第三种主从Reactor多线程模型。</p><h4 id="3-主从Reactor多线程模型"><a href="#3-主从Reactor多线程模型" class="headerlink" title="3.主从Reactor多线程模型"></a>3.主从Reactor多线程模型</h4><p>比起第二种模型,它是将Reactor分成两部分:</p><ol><li>mainReactor负责监听server socket,用来处理网络IO连接建立操作,将建立的socketChannel指定注册给subReactor。</li><li>subReactor主要做和建立起来的socket做数据交互和事件业务处理操作。通常,subReactor个数上可与CPU个数等同。</li></ol><p>Nginx、Swoole、Memcached和Netty都是采用这种实现。</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-10-11_11-54-19.png"></p><p>消息处理流程:</p><ol><li>从主线程池中随机选择一个Reactor线程作为acceptor线程,用于绑定监听端口,接收客户端连接</li><li>acceptor线程接收客户端连接请求之后创建新的SocketChannel,将其注册到主线程池的其它Reactor线程上,由其负责接入认证、IP黑白名单过滤、握手等操作</li><li>步骤2完成之后,业务层的链路正式建立,将SocketChannel从主线程池的Reactor线程的多路复用器上摘除,重新注册到Sub线程池的线程上,并创建一个Handler用于处理各种连接事件</li><li>当有新的事件发生时,SubReactor会调用连接对应的Handler进行响应</li><li>Handler通过Read读取数据后,会分发给后面的Worker线程池进行业务处理</li><li>Worker线程池会分配独立的线程完成真正的业务处理,如何将响应结果发给Handler进行处理</li><li>Handler收到响应结果后通过Send将响应结果返回给Client</li></ol><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>Reactor模型具有如下的优点:</p><ol><li>响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;</li><li>编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;</li><li>可扩展性,可以方便地通过增加Reactor实例个数来充分利用CPU资源;</li><li>可复用性,Reactor模型本身与具体事件处理逻辑无关,具有很高的复用性。</li></ol><p>部分转载:<a href="https://cloud.tencent.com/developer/article/1488120">https://cloud.tencent.com/developer/article/1488120</a></p>]]></content>
<summary type="html"><h1 id="网络IO"><a href="#网络IO" class="headerlink" title="网络IO"></a>网络IO</h1><h2 id="阻塞与非阻塞io"><a href="#阻塞与非阻塞io" class="headerlink" title="阻</summary>
<category term="从0到1" scheme="http://example.com/tags/%E4%BB%8E0%E5%88%B01/"/>
<category term="网络编程" scheme="http://example.com/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>hash与bloomfilter</title>
<link href="http://example.com/2023/09/08/3.hash%E4%B8%8Ebloomfilter/"/>
<id>http://example.com/2023/09/08/3.hash%E4%B8%8Ebloomfilter/</id>
<published>2023-09-07T16:00:00.000Z</published>
<updated>2023-09-13T13:59:37.552Z</updated>
<content type="html"><![CDATA[<h1 id="hash与bloomfilter"><a href="#hash与bloomfilter" class="headerlink" title="hash与bloomfilter"></a>hash与bloomfilter</h1><h2 id="hash冲突的处理"><a href="#hash冲突的处理" class="headerlink" title="hash冲突的处理"></a>hash冲突的处理</h2><p>链表法:</p><blockquote><p>引用链表来处理哈希冲突;也就是将冲突元素用链表链接起来;这也是常用的处理冲突的方式;但是可能出现一种极端情 况,冲突元素比较多,该冲突链表过长,这个时候可以将这个链表转换为红黑树、最小堆;由原来链表时间复杂度转 换为红黑树时间复杂度 ;那么判断该链表过长的依据是多少?可以采用超过 256(经验值)个节点的时候将链表结构转换为红黑树或堆结构</p></blockquote><p>开放寻址法:</p><blockquote><p>将所有的元素都存放在哈希表的数组中,不使用额外的数据结构;一般使用线性探查的思路解决;</p><ol><li><p>当插入新元素的时,使用哈希函数在哈希表中定位元素位置</p></li><li><p>检查数组中该槽位索引是否存在元素。如果该槽位为空,则 插入,否则3;</p></li><li><p>在 2 检测的槽位索引上加一定步长接着检查2; 加一定步长 分为以下几种:</p><ul><li>i+1,i+2,i+3,i+4, … ,i+n </li><li>i-1^2 , i+2^2 ,i-3^2 ,1+4^2 , …</li></ul><p>这两种都会导致同类 hash 聚集;也就是近似值它的hash值也近似,那么它的数组槽 位也靠近,形成 hash 聚集;第一种同类聚集冲突在前, 第二种只是将聚集冲突延后; 另外还可以使用<strong>双重哈希</strong>来解决上面出现hash聚集现象:</p></li></ol></blockquote><p>当负载因子不在合理范围如:<code>userd / size > 0.9</code><code>userd /size < 0.1</code> 要进行适当的扩容或缩容,在进行完扩容或缩容后要进行<code>rehash</code></p><h2 id="布隆过滤器"><a href="#布隆过滤器" class="headerlink" title="布隆过滤器"></a>布隆过滤器</h2><p>有些时候,由于内粗是有限的,只想确定key是否存在,而关心<code>value</code>的内容,这样就可以使用<code>bloom_filter</code>,布隆过滤器,由位图实现</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-09-08_18-33-32.png"></p><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><p>当一个元素加入位图时,通过 k 个 hash 函数将这个元素映射到位图的 k 个点,并把它们置为 1;当检索时,再通过 k 个 hash 函数运算检测位图的 k 个点是否都为 1;如果有不为 1 的点,那么认为该 key 不存在;<strong>如果全部为 1,则可能存在</strong>,当一个key不存在时是可以通过布隆过滤器确认的</p><p>在位图中每个槽位只有两种状态(0 或者 1),一个槽位被 设置为 1 状态,但不确定它被设置了多少次;也就是不知道被多少个 key 哈希映射而来以及是被具体哪个 hash 函数映射而来,所以布隆过滤器<strong>不支持删除操作</strong></p><p>在实际应用中,该选择多少个 hash 函数?要分配多少空间的位 图?预期存储多少元素?如何控制误差?</p><blockquote><p>n – 预期布隆过滤器中元素的个数 </p><p>p – 假阳率,在0-1之间 0.000000 </p><p>m – 位图所占空间 </p><p>k – hash函数的个数 </p><p>公式如下:<br>n = ceil(m / (-k / log(1 - exp(log(p) / k))))<br>p = pow(1 - exp(-k / (m / n)), k)<br>m = ceil((n * log(p)) / log(1 / pow(2, log(2))));<br>k = round((m / n) * log(2));</p></blockquote><p>常通过<a href="https://hur.st/bloomfilter/">Bloom filter calculator (hur.st)</a>站点,输入n 和 p 计算得到所需要的bit位和哈希函数的数量</p><p>如何只用2G内存在20亿整数中寻找出现最多的数?</p><blockquote><p>首先想到散列表,存储k v键值对,v的最大值为20亿,故uint32可以满足,故一个kv占字节,这里有20亿个,故占用16GB,明显超出2G,想办法将20亿整数拆分到若干个文件中,当然不能盲目拆分,要将相同的值放到同一个文件中,可以使用hash函数来解决这个问题(相同的值经过同一个hash函数会得到相同的结果),将hash函数的结果对文件数取余,存放到对应的文件中</p><p><strong>大文件可以用hash拆为小文件</strong></p><p><strong>单台机器无法解决,通过hash分流到多台机器</strong></p></blockquote><h2 id="分布式一致性-hash"><a href="#分布式一致性-hash" class="headerlink" title="分布式一致性 hash"></a>分布式一致性 hash</h2><h3 id="问题:"><a href="#问题:" class="headerlink" title="问题:"></a>问题:</h3><p>在主服务器从数据缓存服务器中请求数据时,往往根据数据的key求出的hash值与服务器的个数取余,以此来找到对应的服务器,当扩充数据缓存服务器时,就出现了大问题,由于服务器增加了故模数要变化,就导致对应的key找不到正确的数据服务器</p><h3 id="解决:"><a href="#解决:" class="headerlink" title="解决:"></a>解决:</h3><p> 分布式一致性 hash 算法将哈希空间组织成一个虚拟的圆环,圆 环的大小是 ;</p><p>算法为:<code>hash(ip)</code> ,最终会得到一个 [0, ] 之间的一个无符号整型,这个整数代表服务器的编号;多个服务器都通过这种方式在 <code>hash </code>环上映射一个点来标识该服务器的位置;当用户操作某个<code> key</code>,通过同样的算法生成一个值,沿环顺时针定位某个服务器,那么该 <code>key</code> 就在该服务器中;</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/%E4%B8%80%E8%87%B4%E6%80%A7%E5%93%88%E5%B8%8C.png"></p><p>此时有三台数据服务器,以及四个数据,<code>k1 -> 10.0.0.2k2->10.0.0.3k3 k4 -> 10.0.0.1</code></p><p>当增加一个服务器时</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/%E4%B8%80%E8%87%B4%E6%80%A7%E5%93%88%E5%B8%8C%E6%89%A9%E5%AE%B9.png"></p><p>此时寻找 k3 会到 10.0.0.5 中寻找,但是k3存储在 10.0.0.1 中,这就造成了<strong>局部失效</strong>,虽然无法根治问题,但是只是局部失效,原方法会造成大面积的数据失效,为了解决数据失效,要<strong>进行部分的数据迁移</strong>,即:将hash后结果落在 10.0.0.3 到10.0.0.5 服务器对应hash值之间的 key 存储到新加的服务器中,并在原服务器中删除,这个hash值直接在原服务器中寻找就好了</p><h3 id="虚拟节点"><a href="#虚拟节点" class="headerlink" title="虚拟节点"></a>虚拟节点</h3><p>当我们的服务器过少时,节点无法均匀的分散在圆环上,这样会导致某一个服务器上存放了大量数据</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/%E8%99%9A%E6%8B%9F%E8%8A%82%E7%82%B9.png"></p><p>为了解决这种问题,我们采用虚拟节点,即在对应的<code>ip:port</code>后添加 <code>: 编号</code>,编号从 1 - 255 ,这样一个服务器就产生了255个节点,使得节点的分布根据随机性,在数据存储时,只需将本应该存储在对应虚拟节点的数据存储在其本身节点上即可,在进行数据查找时,找到对应的<code>ip:port:编号</code>,只需将编号截取掉就是存放数据的服务器,这样解决了数据分配不均问题也使得在扩容时进行hash迁移的数据减少了</p>]]></content>
<summary type="html"><h1 id="hash与bloomfilter"><a href="#hash与bloomfilter" class="headerlink" title="hash与bloomfilter"></a>hash与bloomfilter</h1><h2 id="hash冲突的处理</summary>
<category term="从0到1" scheme="http://example.com/tags/%E4%BB%8E0%E5%88%B01/"/>
<category term="数据结构" scheme="http://example.com/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
</entry>
<entry>
<title>红黑树</title>
<link href="http://example.com/2023/09/04/%E7%BA%A2%E9%BB%91%E6%A0%91/"/>
<id>http://example.com/2023/09/04/%E7%BA%A2%E9%BB%91%E6%A0%91/</id>
<published>2023-09-03T16:00:00.000Z</published>
<updated>2023-09-13T13:59:46.711Z</updated>
<content type="html"><![CDATA[<h1 id="红黑树"><a href="#红黑树" class="headerlink" title="红黑树"></a>红黑树</h1><h2 id="红黑色的定义"><a href="#红黑色的定义" class="headerlink" title="红黑色的定义"></a>红黑色的定义</h2><blockquote><ol><li>每个结点是红的或者黑的</li><li>根节点是黑的所有叶子节点是黑色(叶子节点是空节点)</li><li>如果一个节点是红的,则他的两个儿子节点都是黑的</li><li>对每个节点,从该节点到子孙节点的所有路径上的包含相同数量的黑节点</li></ol></blockquote><h2 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h2><h3 id="结构定义:"><a href="#结构定义:" class="headerlink" title="结构定义:"></a>结构定义:</h3><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="type">int</span> KEY_TYPE; <span class="comment">//防止key类型写死</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">rbtree_node</span> {</span><span class="comment">//节点定义</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">char</span> color;<span class="comment">//节点颜色</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> _<span class="title">rb_tree_node</span> *<span class="title">right</span>;</span><span class="comment">//左右子树</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> _<span class="title">rb_tree_node</span> *<span class="title">left</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> _<span class="title">rb_tree_node</span> *<span class="title">parent</span>;</span> <span class="comment">//指向父节点,用于性质调整</span></span><br><span class="line"></span><br><span class="line"> KEY_TYPE key;</span><br><span class="line"> <span class="type">void</span> *value;</span><br><span class="line">} rb_tree_node;</span><br></pre></td></tr></table></figure><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">rbtree</span>{</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> _<span class="title">rbtree_node</span>* <span class="title">root</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> _<span class="title">rbtree_node</span>* <span class="title">nil</span>;</span> </span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>红黑树所有的叶子节点都可以是<strong>隐藏</strong>的且都是黑色的,竟然是隐藏的,那么就可以只有一个叶子节点,即需要叶子节点时均采用指向该叶子节点的指针,这里的叶子节点即为<code>nil</code>所指向的节点,这里不将叶子节点设为<code>NULL</code>是因为nil中具备节点所有的属性,拥有<code>parent</code>,防止了内存的非法访问</p><p>当我们看到拥有颜色,左右子树,父节点指针时,那么这个数据结构就是一颗红黑树,我们上面写的红黑树结构是不可复用的,因为,我们将业务(key value)和红黑树的实现(左右子树,颜色,父节点)放在一起了,于是,我们将红黑树的性质剥离出来</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">typedef</span> <span class="type">int</span> KEY_TYPE;</span><br><span class="line"><span class="comment">//在使用红黑树时,使用这个宏即可</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> RBTREE_ENTRY(name , type) \ </span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">name</span> {</span> \</span><br><span class="line"> <span class="type">unsigned</span> <span class="type">char</span> color;\</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">type</span> *<span class="title">right</span>;</span>\</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">type</span> *<span class="title">left</span>;</span>\</span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">type</span> *<span class="title">parent</span>;</span>\</span><br><span class="line"> } <span class="comment">//无分号</span></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">rbtree_node</span> {</span></span><br><span class="line"> KEY_TYPE key;</span><br><span class="line"> <span class="type">void</span> *value;</span><br><span class="line"> </span><br><span class="line"> RBTREE_ENTRY( , rb_tree_node);<span class="comment">//匿名结构体,相当于释放</span></span><br><span class="line"> <span class="meta">#<span class="keyword">if</span> 0</span></span><br><span class="line"> RBTREE_ENTRY( , rb_tree_node) name1;<span class="comment">//节点内也可以有</span></span><br><span class="line"> RBTREE_ENTRY( , rb_tree_node) name2;</span><br><span class="line"> <span class="meta">#<span class="keyword">endif</span></span></span><br><span class="line"> </span><br><span class="line">} rb_tree_node;</span><br><span class="line"></span><br><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">rbtree</span>{</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> _<span class="title">rbtree_node</span>* <span class="title">root</span>;</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> _<span class="title">rbtree_node</span>* <span class="title">nil</span>;</span> <span class="comment">//所有的叶子节点黑色null</span></span><br><span class="line">};</span><br></pre></td></tr></table></figure><h3 id="旋转"><a href="#旋转" class="headerlink" title="旋转"></a>旋转</h3><p>当红黑树性质不满足时就要进行旋转操作</p><p><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-09-04_15-10-30.png"></p><p> 左旋:</p><blockquote><p>x的右子树指向y的左子树b</p><p>y的左子树指向x</p><p>x的parent的左子树或右子树指向y</p></blockquote><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">rbtree_left_rotate</span><span class="params">(rbtree* T , rb_tree_node* x)</span> {</span><br><span class="line"> rb_tree_node* y = x->right;</span><br><span class="line"> x->right = y->left;</span><br><span class="line"> <span class="keyword">if</span> (y->left != T->nil) <span class="comment">//当y的左子树不为叶子节点时,修改左子树的父节点</span></span><br><span class="line"> y->left->parent = x; </span><br><span class="line"> </span><br><span class="line"> y->parent = x->parent;</span><br><span class="line"> <span class="keyword">if</span>(x->parent == T->nil) { <span class="comment">//x为根节点,根节点的父节点为空节点nil</span></span><br><span class="line"> T->root = y;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (x == x->parent->left) { </span><br><span class="line"> x->parent->left = y;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> x->parent->right = y;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> y->left = x;</span><br><span class="line"> x->parent = y;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h3><p>插入的节点都会在最底层(除叶子节点)。红黑树在插入节点以前,它已经是一颗红黑树了,在插入时要尽量少改变原有红黑树的性质,故插入节点初始上色为红色,这样不会影响从任意节点到子孙节点的所有路径上的包含相同数量的黑节点</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">rbtree_insert</span><span class="params">(rbtree *T , rbtree_node* z)</span> {</span><br><span class="line"> rbtree_node* y = T->nil;</span><br><span class="line"> rbtree_node *x = T->root;</span><br><span class="line"> <span class="keyword">while</span> (x != T->nil) { </span><br><span class="line"> y = x;</span><br><span class="line"> <span class="keyword">if</span>(z->key < x->key) {</span><br><span class="line"> x = x->left;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (z->key > x->key){</span><br><span class="line"> x = x->right;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(y == T->nil) {</span><br><span class="line"> T->root = z;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(y->key > z->key) {</span><br><span class="line"> y->left = z;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> y->right = z;</span><br><span class="line"> }</span><br><span class="line"> z->parent = y; </span><br><span class="line"> z->left = T->nil;</span><br><span class="line"> z->right = T->nil;</span><br><span class="line"> z->color = RED;</span><br><span class="line"></span><br><span class="line"> rbtree_insert_fixup(T , z);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>调整:</p><p>在调整前我们可以知道,z是红色,z的父节点是红色,z的祖父节点是黑色,z的叔叔节点不确定。于是我们可以根据z的叔叔节点颜色进行分类讨论</p><ol><li>叔叔节点是红色<br><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-09-04_20-53-02.png"></li><li>叔结点是黑色的,而且当前结点是右孩子<br><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-09-04_20-54-03.png"></li><li>. 叔结点是黑色的,而且当前结点是左孩子<br><img src="/img/%E4%BB%8E0%E5%88%B01/Snipaste_2023-09-04_20-54-33.png"></li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">rbtree_insert_fixup</span><span class="params">(rbtree *T, rbtree_node *z)</span></span><br><span class="line">{</span><br><span class="line"> <span class="keyword">while</span> (z->parent->color == RED)</span><br><span class="line"> { <span class="comment">// 插入节点为红色,且其父节点也为红色,需要调整</span></span><br><span class="line"> <span class="keyword">if</span> (z->parent == z->parent->parent->left)</span><br><span class="line"> { <span class="comment">// 父节点在祖父节点的左子树</span></span><br><span class="line"> rbtree_node *y = z->parent->parent->right; <span class="comment">// 叔叔节点</span></span><br><span class="line"> <span class="keyword">if</span> (y->color == RED)</span><br><span class="line"> { <span class="comment">// 叔叔节点是红色的</span></span><br><span class="line"> z->parent->color = BLACK;</span><br><span class="line"> z->parent->parent->color = RED;</span><br><span class="line"> y->color = BLACK;</span><br><span class="line"></span><br><span class="line"> z = z->parent->parent; <span class="comment">// z时刻保持是红色的</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span></span><br><span class="line"> { <span class="comment">// 叔叔节点是黑色的</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (z == z->parent->right)</span><br><span class="line"> {</span><br><span class="line"> z = z->parent;</span><br><span class="line"> rbtree_left_rotate(T, z);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> z->parent->color = BLACK;</span><br><span class="line"> z->parent->parent->color = RED;</span><br><span class="line"> rbtree_right_rotat(T, z->parent->parent);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="红黑树"><a href="#红黑树" class="headerlink" title="红黑树"></a>红黑树</h1><h2 id="红黑色的定义"><a href="#红黑色的定义" class="headerlink" title="红黑色的定义"><</summary>
<category term="从0到1" scheme="http://example.com/tags/%E4%BB%8E0%E5%88%B01/"/>
<category term="数据结构" scheme="http://example.com/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
</entry>
<entry>
<title>硬盘与显卡的访问与控制</title>
<link href="http://example.com/2023/08/19/5.%E7%A1%AC%E7%9B%98%E4%B8%8E%E6%98%BE%E5%8D%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E4%B8%8E%E6%8E%A7%E5%88%B6/"/>
<id>http://example.com/2023/08/19/5.%E7%A1%AC%E7%9B%98%E4%B8%8E%E6%98%BE%E5%8D%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E4%B8%8E%E6%8E%A7%E5%88%B6/</id>
<published>2023-08-18T16:00:00.000Z</published>
<updated>2023-08-19T03:18:29.713Z</updated>
<content type="html"><![CDATA[<h1 id="硬盘的访问与控制"><a href="#硬盘的访问与控制" class="headerlink" title="硬盘的访问与控制"></a>硬盘的访问与控制</h1><h2 id="给汇编程序分段"><a href="#给汇编程序分段" class="headerlink" title="给汇编程序分段"></a>给汇编程序分段</h2><p>section是nasm汇编编译器的关键字。首先nasm可以理解以汇编编译器程序,主要进行汇编语言的编译,也就是生成机器码。section成为节,主要是为了对程序进行模块化的划分,是汇编程序的结构更加的清晰。section的几个参数我们要着重了解一下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">section 段名 align=对齐倍数 vstart=设置</span><br></pre></td></tr></table></figure><p>若没有align子句在32位和64位程序中段与段间按照4字节对齐,及在原有段后补充0</p><p>若没有vstart子句,则段内汇编地址就是相对程序开的的偏移量,若指定vstart则段内汇编地址从vstart开始</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">section data1 align=16 vstart=0</span><br><span class="line">mydata dw 0xface</span><br><span class="line"></span><br><span class="line">section data2 align=16 vstart=0</span><br><span class="line">string db 'hello'</span><br><span class="line"> </span><br><span class="line">section code align=16 vstart=0</span><br><span class="line"> mov bx , mydata</span><br><span class="line"> mov si , string</span><br></pre></td></tr></table></figure><p>正如我们刚刚讨论过的,每个段都有一个汇编地址,它是相对于整 个程序开头(0)的。为了方便取得该段的汇编地址,NASM 编译器提供 了以下的表达式,可以用在你的程序中:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">section.段名称.start</span><br></pre></td></tr></table></figure><p>段 “code” 相 对 于 整 个 程 序 开 头 的 汇 编 地 址 是 section.code.start。</p><h2 id="加载器和用户程序"><a href="#加载器和用户程序" class="headerlink" title="加载器和用户程序"></a>加载器和用户程序</h2><p>一般来说,加载器和用户程序是在不同的时间、不同的地方,由不同的人或公司开发的。这就意味着,它们彼此并不了解对方的结构和功能。事实上,也不需要了解。加载器必须了解一 些必要的信息,虽然不是很多,但足以知道如何加载用户程序,他们之间必须有一个协议,或者说协定,比如说,在用户程序内部的某个固定位置,包含一些基本的结构信息,每个用户程序都必须把自己的情况放在这里,而加载器也固定在这个位置读取。经验表明,把这个约定的地点放在用户程序的开头,对双方,特别是对加载器来说比较方便,这就是用户程序头部。</p><p>头部需要在源程序以一个段的形式出现<code>section header vestart=0</code>而且,因为它是“头部”,所以,该段当然必须是第一个被定义的段, 且总是位于整个源程序的开头。</p><p>用户程序头部起码要包含以下信息。</p><ol><li>用户程序的尺寸,即以字节为单位的大小。这对加载器来说是很 重要的,加载器需要根据这一信息来决定读取多少个逻辑扇区</li><li>应用程序的入口点,包括段地址和偏移地址。加载器并不清楚用 户程序的分段情况,更不知道第一条要执行的指令在用户程序中的位 置。因此,必须在头部给出第一条指令的段地址和偏移地址,这就是所 谓的应用程序入口点</li><li>段重定位表。用户程序可能包含不止一个段,比较大的程序可能 会包含多个代码段和多个数据段。这些段如何使用,是用户程序自己的 事,但前提是程序加载到内存后,每个段的地址必须重新确定一下。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">SECTION header vstart=0 ;定义用户程序头部段 </span><br><span class="line"> program_length dd program_end ;程序总长度[0x00]</span><br><span class="line"> </span><br><span class="line"> ;用户程序入口点</span><br><span class="line"> code_entry dw start ;偏移地址[0x04]</span><br><span class="line"> dd section.code.start ;段地址[0x06] </span><br><span class="line"> </span><br><span class="line"> ;段重定位表项个数[0x0a]</span><br><span class="line"> realloc_tbl_len dw (header_end-realloc_begin)/4</span><br><span class="line"> </span><br><span class="line"> realloc_begin:</span><br><span class="line"> ;段重定位表 </span><br><span class="line"> code_segment dd section.code.start ;[0x0c]</span><br><span class="line"> data_segment dd section.data.start ;[0x14]</span><br><span class="line"> stack_segment dd section.stack.start ;[0x1c]</span><br><span class="line"> </span><br><span class="line">header_end: </span><br></pre></td></tr></table></figure><h3 id="加载器的工作流程"><a href="#加载器的工作流程" class="headerlink" title="加载器的工作流程"></a>加载器的工作流程</h3><blockquote><p>读取用户程序的起始扇区</p><p>把整个用户程序都读入内存</p><p>计算段的物理地址和逻辑地址和段地址(段重定位)</p><p>转移到用户程序执行(将处理器的控制权交给用户程序)</p></blockquote><h2 id="输入输出端口的访问"><a href="#输入输出端口的访问" class="headerlink" title="输入输出端口的访问"></a>输入输出端口的访问</h2><p>处理器是通过端口(Port)来和外围设备打交道的。本质 上,端口就是一些寄存器,类似于处理器内部的寄存器。不同之处仅仅 在于,这些叫做端口的寄存器位于I/O 接口电路中。端口是处理器和外围设备通过I/O 接口交流的窗口,每一个I/O 接口 都可能拥有好几个端口,分别用于不同的目的。端口可以是8 位的,也可以是16 位的或32位</p><p>比如,连接硬盘的 PATA/SATA 接口就有几个端口,分别是命令端口(当向该端口写入0x20 时,表明是从硬盘读数据;写入0x30 时,表明是向硬盘写数据)、状态端口(处理器根据这个端口的数据来判断硬盘工作是否正常,操作是否成功,发生了哪种错误)、参数端口(处理器通过这些端口告诉硬盘读 写的扇区数量,以及起始的逻辑扇区号)和数据端口(通过这个端口连续地取得要读出的数据,或者通过这个端口连续地发送要写入硬盘的数据)。</p><p>端口在不同的计算机系统中有着不同的实现方式。在一些计算机系 统中,端口号是映射到内存地址空间的。比如,0x00000~0xE0000 是 真实的物理内存地址,而0xE0001~0xFFFFF 是从很多I/O 接口那里映 射过来的,当访问这部分地址时,实际上是在访问I/O 接口。</p><p>而在另一些计算机系统中,端口是独立编址的,不和内存发生关系在这种计算机中,处理器的地址线既连接内存,也连接每一个I/O 接口。但是,处理器还有一个特殊的引脚M/IO#,在这 里,“#”表示低电平有效。也就是说,当处理器访问内存时,它会让 M/IO#引脚呈高电平,这里,和内存相关的电路就会打开;相反,如果处理器访问I/O 端口,那么M/IO#引脚呈低平,内存电路被禁止。与此同时,处理器发出的地址和M/IO#信号一起用于打个某个I/O 接口,如果该 I/O 接口分配的端口号与处理器地址相吻合的话。</p><h3 id="in-out-指令"><a href="#in-out-指令" class="headerlink" title="in out 指令"></a>in out 指令</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">in al , dx</span><br><span class="line">in ax , dx</span><br><span class="line">in al , 立即数</span><br><span class="line">in ax , 立即数</span><br></pre></td></tr></table></figure><p>in 指令的目的操作数必须是寄存器AL 或者AX,当访问8 位的端口时,使用寄存器AL;访问16 位的端口时,使用AX。in 指令的源操作数应当是寄存器DX,in 指令不允许使用别的通用寄存器,也不允许使用内存单元作为操作数。</p><p>in指令的目的操作数是立即数时,只能访问0~255(0x00~0xff)号端口,<strong>不允许访问大于255 的端口号</strong></p><p>out 指令正好和in 指令相反,目的操作数可以是8 位立即数或者寄存器DX,源操作数必须是寄存器AL 或者AX</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">out 0x37 , al ;写0x37号端口(8位端口)</span><br><span class="line">out 0xf5 , ax ;写0xfd号端口(16位端口)</span><br><span class="line">out dx , al ;写一个8位端口,端口号在寄存器dx中</span><br><span class="line">out dx , ax ;写一个16位端口,端口号在寄存器dx中</span><br></pre></td></tr></table></figure><p>in out指令不影响flag寄存器</p><h2 id="通过硬盘控制器端口读扇区数据"><a href="#通过硬盘控制器端口读扇区数据" class="headerlink" title="通过硬盘控制器端口读扇区数据"></a>通过硬盘控制器端口读扇区数据</h2><p>硬盘读写的基本单位是扇区。就是说,要读就至少读一个扇区,要写就至少写一个扇区</p><p>LBA模式(Logical Block Addressing)采用逻辑扇区号的方式访问硬盘,采用LBA28访问硬盘及扇区号由28位bit决定</p><p>主硬盘分配器分配了8个端口(0x1f0 ~ 0x1f7)</p><ol><li><p>设置要读取的扇区数量</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mov dx , 0x1f2 ;访问0x1f2端口</span><br><span class="line">mov al , 0x01 ;设置扇区数量,当al中是0时意味着尧都区的扇区数为256</span><br><span class="line">out dx , al ;设置读取的扇区数为1</span><br></pre></td></tr></table></figure></li><li><p>设置起始的LBA扇区号<br><img src="/img/%5B19.9%5D--%E9%80%9A%E8%BF%87%E7%A1%AC%E7%9B%98%E6%8E%A7%E5%88%B6%E5%99%A8%E7%AB%AF%E5%8F%A3%E8%AF%BB%E6%89%87%E5%8C%BA%E6%95%B0%E6%8D%AE.pcwlenv_%E8%A7%86%E9%A2%91%E6%88%AA%E5%9B%BE_304.jpg"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">;扇区号0x0 00 00 02</span><br><span class="line">mov dx , 0x1f3</span><br><span class="line">mov al , 0x02 ;LBA地址 7~0</span><br><span class="line">out dx , al </span><br><span class="line">inc dx;0x1f4</span><br><span class="line">mov al , 0x00</span><br><span class="line">out dx , al</span><br><span class="line">inc dx;0x1f5</span><br><span class="line">out dx , al</span><br><span class="line">inc dx;0x1f6</span><br><span class="line">mov al , 0xe0;高8位 1110 第四位为0表示读写主硬盘,第六位位1表示采用LBA模式,7 5位固定为1</span><br><span class="line">out dx , al</span><br></pre></td></tr></table></figure></li><li><p>设置读命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mov dx , 0x1f7</span><br><span class="line">mov al , 0x20</span><br><span class="line">out dx , al</span><br></pre></td></tr></table></figure></li><li><p>等待读写完成<br><img src="/img/%5B19.9%5D--%E9%80%9A%E8%BF%87%E7%A1%AC%E7%9B%98%E6%8E%A7%E5%88%B6%E5%99%A8%E7%AB%AF%E5%8F%A3%E8%AF%BB%E6%89%87%E5%8C%BA%E6%95%B0%E6%8D%AE.pcwlenv_%E8%A7%86%E9%A2%91%E6%88%AA%E5%9B%BE_814.jpg"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">mov dx , 0x1f7</span><br><span class="line">.waits:</span><br><span class="line">in al , dx</span><br><span class="line">and al , 0x88</span><br><span class="line">cmp al , 0x08 ;当无错误且准备好时不跳转</span><br><span class="line">jnz .waits</span><br></pre></td></tr></table></figure></li><li><p>读硬盘</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">;假定DS已指向存放扇区数据的段,BX里时段内的偏移地址</span><br><span class="line">mov cx , 256</span><br><span class="line">mov dx , 0x1f0</span><br><span class="line">.readw:</span><br><span class="line">in ax , dx</span><br><span class="line">mov [bx] , ax</span><br><span class="line">add bx , 2</span><br><span class="line">loop .readw</span><br></pre></td></tr></table></figure></li></ol><h2 id="比特位移动指令"><a href="#比特位移动指令" class="headerlink" title="比特位移动指令"></a>比特位移动指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">calc_segment_base: ;计算16位段地址</span><br><span class="line"> ;输入:DX:AX 返回AX</span><br><span class="line"> push dx</span><br><span class="line"> </span><br><span class="line"> add ax , [cs:phy_base]</span><br><span class="line"> adc dx , [cs:phy_base+0x02]</span><br><span class="line"> shr ax , 4</span><br><span class="line"> ror dx , 4</span><br><span class="line"> and ax , dx</span><br><span class="line"> or ax , dx</span><br><span class="line"> pop dx</span><br></pre></td></tr></table></figure><p>8086最大支持1M内存寻址,故地址有20位。我们将段地址高字节存放在寄存器<code>dx</code>低字节存放在寄存器<code>ax</code>,由于只能有20位故<code>dx</code>的高12位为0,由于是段地址故<code>ax</code>的第四位为0</p><h3 id="add-adr"><a href="#add-adr" class="headerlink" title="add adr"></a>add adr</h3><p>8086无法进行32位加法,需要<code>add</code>和<code>adc</code>配合使用,<code>adc</code>是带进位的加法指令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">add ax , [cs:phy_base]</span><br><span class="line">adc dx , [cs:phy_base+0x02]</span><br></pre></td></tr></table></figure><p>在进行add后有可能产生进位,导致标志寄存器<code>CF</code>有可能为1,<code>adc</code>指令除了将操作数相加外还要加标志寄存器<code>CF</code></p><h3 id="shr-ror-shl-rol"><a href="#shr-ror-shl-rol" class="headerlink" title="shr ror shl rol"></a>shr ror shl rol</h3><p>逻辑右移指令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">shr 寄存器/内存 , 立即数(8位)</span><br><span class="line">shr 寄存器/内存 , cl(存放移动的位数)</span><br></pre></td></tr></table></figure><p>空余bit用0填充,标志寄存器CF=最后一个被移出的bit</p><p><img src="/img/%5B19.14%5D--%E6%AF%94%E7%89%B9%E4%BD%8D%E7%9A%84%E7%A7%BB%E5%8A%A8%E6%8C%87%E4%BB%A4.pcwlenv_%E8%A7%86%E9%A2%91%E6%88%AA%E5%9B%BE_898.jpg"></p><p>循环右移指令ror</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ror 寄存器/内存 , 立即数(8位)</span><br><span class="line">ror 寄存器/内存 , cl(存放移动的位数)</span><br></pre></td></tr></table></figure><p><img src="/img/%5B19.14%5D--%E6%AF%94%E7%89%B9%E4%BD%8D%E7%9A%84%E7%A7%BB%E5%8A%A8%E6%8C%87%E4%BB%A4.pcwlenv_%E8%A7%86%E9%A2%91%E6%88%AA%E5%9B%BE_1001.jpg"></p><p>与shr ror相对应的是shl rol 逻辑左移和循环左移,<code>sh -> shift(挪动)</code> <code>ro -> round圆</code></p><h2 id="无条件转移指令"><a href="#无条件转移指令" class="headerlink" title="无条件转移指令"></a>无条件转移指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">jmp short 标号 ;机器码 EB 一字节相对地址</span><br><span class="line">jmp near 标号;机器码 E9 一个字相对地址</span><br><span class="line">jmp 标号;编译器根据距离目标位置的远近决定使用近转移还是短转移</span><br></pre></td></tr></table></figure><p>16位间接近转移</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmp 寄存器/内存 ;直接转移到目标位置</span><br></pre></td></tr></table></figure><p>16位绝对远转移</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmp 段地址:偏移地址</span><br></pre></td></tr></table></figure><p>16位间接绝对远转移</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">jmp far 内存;在指定的内存地址处必须包含目标位置的段地址和偏移地址,第一个字是偏移地址ip,第二个字是段地址cs</span><br></pre></td></tr></table></figure><h2 id="内存保留指令"><a href="#内存保留指令" class="headerlink" title="内存保留指令"></a>内存保留指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">resb 立即数 :保留立即数字节的内存,不初始化</span><br><span class="line"></span><br><span class="line">resw 立即数:保留立即数字的内存,不初始化</span><br><span class="line"></span><br><span class="line">resd 立即数:保留立即数双字的内存,不初始化</span><br></pre></td></tr></table></figure><h2 id="retf指令"><a href="#retf指令" class="headerlink" title="retf指令"></a>retf指令</h2><p>CPU执行retf指令时,进行下面两步操作:</p><ol><li>(IP) = ((ss) * 16 + (sp))</li><li>(SP) = (sp) + 2</li><li>(CS) = ((ss) * 16 + (sp))</li><li>(SP) = (sp) + 2</li></ol>]]></content>
<summary type="html"><h1 id="硬盘的访问与控制"><a href="#硬盘的访问与控制" class="headerlink" title="硬盘的访问与控制"></a>硬盘的访问与控制</h1><h2 id="给汇编程序分段"><a href="#给汇编程序分段" class="header</summary>
<category term="汇编语言" scheme="http://example.com/tags/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/"/>
</entry>
<entry>
<title>栈指令及寻址方式</title>
<link href="http://example.com/2023/08/04/4.%E6%A0%88%E6%8C%87%E4%BB%A4%E5%8F%8A%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F/"/>
<id>http://example.com/2023/08/04/4.%E6%A0%88%E6%8C%87%E4%BB%A4%E5%8F%8A%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F/</id>
<published>2023-08-03T16:00:00.000Z</published>
<updated>2023-08-04T06:46:26.053Z</updated>
<content type="html"><![CDATA[<h1 id="栈指令及寻址方式"><a href="#栈指令及寻址方式" class="headerlink" title="栈指令及寻址方式"></a>栈指令及寻址方式</h1><h2 id="栈指令:"><a href="#栈指令:" class="headerlink" title="栈指令:"></a>栈指令:</h2><p>寄存器ss内存放着栈段地址,寄存器sp中保存着相对栈段寄存器ss的偏移地址</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">push 寄存器/内存</span><br></pre></td></tr></table></figure><ol><li>sp = (sp - 操作数的大小) ,在16位8086中压栈出栈必须是一个字</li><li>段寄存器ss左移4位,加上sp的偏移地址,生成物理地址</li><li>将操作数写入上述地址中</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pop 寄存器/内存</span><br></pre></td></tr></table></figure><ol><li>段寄存器ss左移4位,加上sp里的偏移地址,生成物理地址</li><li>从上述地址处取得数据,存入由操作数体统的目标位置处</li><li>sp = sp + 2</li></ol><h2 id="逻辑指令"><a href="#逻辑指令" class="headerlink" title="逻辑指令"></a>逻辑指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">or 寄存器/内存 , 寄存器/内存/立即数</span><br><span class="line">and 寄存器/内存 , 寄存器/内存/立即数</span><br></pre></td></tr></table></figure><p>在执行l逻辑指令后:</p><blockquote><p>OF = 0CF = 0</p><p>SF ZF PF依据计算结果而定,AF的状态未定义</p></blockquote><h2 id="从1加到100并显示结果"><a href="#从1加到100并显示结果" class="headerlink" title="从1加到100并显示结果"></a>从1加到100并显示结果</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line">;从1加到100并显示累加结果</span><br><span class="line"> jmp near start</span><br><span class="line"></span><br><span class="line"> message db '1+2+3+...+100='</span><br><span class="line"> </span><br><span class="line"> start:</span><br><span class="line"> mov ax,0x7c0 ;设置数据段的段基地址 </span><br><span class="line"> mov ds,ax</span><br><span class="line"></span><br><span class="line"> mov ax,0xb800 ;设置附加段基址到显示缓冲区</span><br><span class="line"> mov es,ax</span><br><span class="line"></span><br><span class="line"> ;以下显示字符串 </span><br><span class="line"> mov si,message </span><br><span class="line"> mov di,0</span><br><span class="line"> mov cx,start-message</span><br><span class="line"> @g:</span><br><span class="line"> mov al,[si]</span><br><span class="line"> mov [es:di],al</span><br><span class="line"> inc di</span><br><span class="line"> mov byte [es:di],0x07</span><br><span class="line"> inc di</span><br><span class="line"> inc si</span><br><span class="line"> loop @g</span><br><span class="line"></span><br><span class="line"> ;以下计算1到100的和 </span><br><span class="line"> xor ax,ax</span><br><span class="line"> mov cx,1</span><br><span class="line"> @f:</span><br><span class="line"> add ax,cx</span><br><span class="line"> inc cx</span><br><span class="line"> cmp cx,100</span><br><span class="line"> jle @f</span><br><span class="line"></span><br><span class="line"> ;以下计算累加和的每个数位 </span><br><span class="line"> xor cx,cx ;设置堆栈段的段基地址</span><br><span class="line"> mov ss,cx</span><br><span class="line"> mov sp,cx</span><br><span class="line"></span><br><span class="line"> mov bx,10</span><br><span class="line"> xor cx,cx</span><br><span class="line"> @d:</span><br><span class="line"> inc cx</span><br><span class="line"> xor dx,dx</span><br><span class="line"> div bx</span><br><span class="line"> or dl,0x30</span><br><span class="line"> push dx</span><br><span class="line"> cmp ax,0</span><br><span class="line"> jne @d</span><br><span class="line"></span><br><span class="line"> ;以下显示各个数位 </span><br><span class="line"> @a:</span><br><span class="line"> pop dx</span><br><span class="line"> mov [es:di],dl</span><br><span class="line"> inc di</span><br><span class="line"> mov byte [es:di],0x07</span><br><span class="line"> inc di</span><br><span class="line"> loop @a</span><br><span class="line"> </span><br><span class="line"> jmp near $ </span><br><span class="line"> </span><br><span class="line">times 510-($-$$) db 0</span><br><span class="line"> db 0x55,0xaa</span><br></pre></td></tr></table></figure><h2 id="基址寻址"><a href="#基址寻址" class="headerlink" title="基址寻址"></a>基址寻址</h2><p>基址寄存器 bx bp</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mov dx , [bp+2];ss为段寄存器</span><br><span class="line">mov dx , [bx+2];bs为栈寄存器</span><br></pre></td></tr></table></figure><p>不需要使用段超越前缀ss,当使用基址寄存器bp进行寻址时,默认将寄存器ss中内存作为段寄存器</p><h2 id="变址寻址"><a href="#变址寻址" class="headerlink" title="变址寻址"></a>变址寻址</h2><p>变址寄存器/索引寄存器 si di</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mov [si+0x10] , dx</span><br><span class="line">add ax , [di]</span><br><span class="line">xor word [si] , 0x800</span><br></pre></td></tr></table></figure><p>当指令中使用了变址寄存器并且没有使用段超越前缀,默认以bx中作为段地址</p><h2 id="基址变址寻址"><a href="#基址变址寻址" class="headerlink" title="基址变址寻址"></a>基址变址寻址</h2><p>当使用基址寄存器bs时,默认使用段寄存器bx作为段地址</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[bx + si]</span><br><span class="line">[bx + di]</span><br><span class="line">[bx + si + 偏移量]</span><br><span class="line">[bx + di + 偏移量]</span><br></pre></td></tr></table></figure><p>当使用基址寄存器bp时,默认使用段寄存器ss作为段地址</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[bp + si]</span><br><span class="line">[bp + di]</span><br><span class="line">[bp + si + 偏移量]</span><br><span class="line">[bp + di + 偏移量]</span><br></pre></td></tr></table></figure><h2 id="就地反转字符串内容"><a href="#就地反转字符串内容" class="headerlink" title="就地反转字符串内容"></a>就地反转字符串内容</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"> jmp start;</span><br><span class="line"></span><br><span class="line">string db 'abcdefghigklmnopqrstuvwxyz'</span><br><span class="line"></span><br><span class="line">start: </span><br><span class="line"> mov ax , 0x7c0;</span><br><span class="line"> mov ds , ax;</span><br><span class="line"></span><br><span class="line"> mov bx , string;</span><br><span class="line"> mov si , 0;</span><br><span class="line"> mov di , start - string - 1;</span><br><span class="line"></span><br><span class="line">rever:</span><br><span class="line"> mov ah , [bx + si];</span><br><span class="line"> mov al , [bx + di];</span><br><span class="line"> mov [bx + si] , al;</span><br><span class="line"> mov [bx + di] , ah;</span><br><span class="line"> inc si;</span><br><span class="line"> dec di;</span><br><span class="line"> cmp si , di;</span><br><span class="line"> jl rever;</span><br><span class="line"></span><br><span class="line"> jmp $</span><br><span class="line"></span><br><span class="line"> times 510 - ($-$$) db 0;</span><br><span class="line"> db 0x55 , 0xaa;</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="栈指令及寻址方式"><a href="#栈指令及寻址方式" class="headerlink" title="栈指令及寻址方式"></a>栈指令及寻址方式</h1><h2 id="栈指令:"><a href="#栈指令:" class="headerlink" </summary>
<category term="汇编语言" scheme="http://example.com/tags/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/"/>
</entry>
<entry>
<title>计算机中的负数</title>
<link href="http://example.com/2023/08/01/3.%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%AD%E7%9A%84%E8%B4%9F%E6%95%B0/"/>
<id>http://example.com/2023/08/01/3.%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%AD%E7%9A%84%E8%B4%9F%E6%95%B0/</id>
<published>2023-07-31T16:00:00.000Z</published>
<updated>2023-08-02T15:08:28.678Z</updated>
<content type="html"><![CDATA[<h1 id="计算机中的负数"><a href="#计算机中的负数" class="headerlink" title="计算机中的负数"></a>计算机中的负数</h1><h2 id="sub指令"><a href="#sub指令" class="headerlink" title="sub指令"></a>sub指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sub 寄存器/内存 寄存器/内存/立即数</span><br></pre></td></tr></table></figure><p>将左操作数减去右操作数结果保留在左操作数中</p><p>两个操作数宽度必须一致且不可同时为内存地址</p><h2 id="neg指令"><a href="#neg指令" class="headerlink" title="neg指令"></a>neg指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">neg 寄存器/内存</span><br></pre></td></tr></table></figure><p>计算 0 - 操作数,并将结果写回操作数中</p><h2 id="无符号与有符号的运算"><a href="#无符号与有符号的运算" class="headerlink" title="无符号与有符号的运算"></a>无符号与有符号的运算</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mov ax , -1</span><br><span class="line">mob ax , 65535</span><br></pre></td></tr></table></figure><p>这两条指令导致ax内全为1,但是ax到底表示 -1 还是 65535呢?解释很简单,计算机只是机器并不关心,程序由程序员编写,一个数是否有符号程序员应该清楚</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">mov ah , 0xf0 ;11110000 可以解释为240 / -16</span><br><span class="line">inc ah ;11110001 可以解释为241 / -15</span><br><span class="line"></span><br><span class="line">mov ah , 0xf0 </span><br><span class="line">add ah , 0x03 ;11110011 可以解释为243 / -13</span><br></pre></td></tr></table></figure><ul><li>对于大多数指令既适用于无符号整数,也适用于有符号整数。指令执行的结果不管是用无符号整数来解释还是用用符号整数来解释都是正确的</li><li>但是也有一些指令不能同时应付无符号数和有符号数,需要根据实际情况选择对应的是否有符号版本,如:无符号数乘法<code>mul</code>有符号乘法<code>imul</code>,以及无符号除法<code>div</code>有符号除法<code>idiv</code></li></ul><h2 id="idiv指令"><a href="#idiv指令" class="headerlink" title="idiv指令"></a>idiv指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">idiv 寄存器/内存</span><br></pre></td></tr></table></figure><p>规则诶呀div指令相同</p><ul><li>如果被除数和除数的符号相同,商为正数,否则商为负数</li><li>余数的符号始终和被除数相同</li></ul><h2 id="符号扩展"><a href="#符号扩展" class="headerlink" title="符号扩展"></a>符号扩展</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mov ax , -6002</span><br><span class="line">mov bx , -10</span><br><span class="line">idiv bx</span><br></pre></td></tr></table></figure><p>在执行idiv之前要将ax的符号位扩展到dx上,使用以下指令</p><ul><li>cbw : 将AL中的有符号数扩展到AX</li><li>cwde:将AX中的有符号数扩展到EAX</li><li>cdqe: 将EAX中的有符号数扩展到RAX</li><li>cwd: 将AX中有符号数扩展到DX:AX</li><li>cdq: 将EAX中有符号数扩展到EDX:EAX</li><li>cdo: 将RAX中有符号数扩展到EDX:RAX</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">mov ax , -6002</span><br><span class="line">cwd</span><br><span class="line">mov bx , -10</span><br><span class="line">idiv bx</span><br></pre></td></tr></table></figure><h2 id="flag寄存器"><a href="#flag寄存器" class="headerlink" title="flag寄存器"></a>flag寄存器</h2><p><img src="/img/%5B16.1%5D--8086%E7%9A%84%E6%A0%87%E5%BF%97%E5%AF%84%E5%AD%98%E5%99%A8.pcwlenv_%E8%A7%86%E9%A2%91%E6%88%AA%E5%9B%BE_57.jpg"></p><p>CF(Carry Flag)进位标志:当一个算术操作在结果的最高位产生进位或者借位时,此标志是1否则是0</p><blockquote><p>如 <code>AL </code>内容是二进制 <code>1000 0000 </code>执行指令 <code>add al , al</code> 后,<code>CF = 1</code></p></blockquote><p>PF(parity Flag)奇偶标志:当一个算数操作数的结果在低8位中有偶数个1,此标志为1否则为0</p><blockquote><p>若AL中内容是二进制 0010 0110 则指令</p><p>xor al , 3 执行后PF=0</p></blockquote><p>OF(Overflow Flag)溢出标志位:对任何一个算数操作,<strong>假定它进行的是有符号运算</strong>。那么,当结果超出目标位置所能容纳的最大正数或者最小负数时,此标志为1,表示有符号整数运算结果以及溢出,否则为0</p><blockquote><p>若AH中内容时二进制 1111 1101 则指令</p><p>add ah , 5 执行后,OF = 0</p></blockquote><p>ZF(Zero Flag)零标志:当运算结果为0时,此标志为1,否则为0</p><blockquote><p>mov ax , 25</p><p>sub ax , 25 ;此指令执行后 ZF = 1</p></blockquote><p>SF(Sign Flag):用运算结果的最高位来设置此标志位(一般来说,这一位是有符号数的符号位,0表示正数,1表示负数)</p><blockquote><p>mov ah , 127</p><p>add ah , 1 ;此指令执行后,SF=1</p></blockquote><p>AF(Adjust Flag):当一个算术操作在结果的位3产生进位或者借位时,此标志是1否则是0。此标志用于二进制编码的十进制算法里(BCD编码),用的很少</p><p>现有指令对标志位的影响:</p><ul><li>cbw/cwde/cdqe/cwd/cdq/cqo(位拓展指令),不影响任何标志位</li><li>cld:DF=0,对CF OF ZF SF AF PF的影响未定义</li><li>std:DF=1,不影响其他标志位</li><li>inc/dec:<strong>CF标志不受影响</strong>,对OF SF ZF AF PF的影响依据计算结果</li><li>add/sub:OF SF ZF AF CF PF 的状态依据计算结果而定</li><li>div/idiv:对CF OF SF ZF AF PF 的影响未定义</li><li>mov/movs:不影响任何标志位</li><li>neg:如果操作数为,则CF=0,否则CF=1,对OF SF ZF AF PF的影响依据计算结果</li><li>xor:OF=0 CF=0 对SF ZF PF依照计算结果</li></ul><h2 id="条件转移指令"><a href="#条件转移指令" class="headerlink" title="条件转移指令"></a>条件转移指令</h2><ul><li>js: 符号标志 SF 为1则转移<br>jns:符号标志 SF 为0则转移 </li><li>jz: 零标志ZF为1则转移<br>jnz:零标志ZF为0则转移</li><li>jo: 溢出标志位OF为1则转移<br>jno:溢出标志位OF为1则转移</li><li>jc: 进位标志位CF为1则转移<br>jnc:进位标志位CF为0则转移</li><li>jp: 奇偶标志PF为1则转移<br>jnp:奇偶标志PF为0则转移</li><li>jcxz(jump if CX is Zero):当CX寄存器中的内容为0时则转移。执行这条指令时,处理器先测试CX是否为0<br><code>jcxz show ;若cx为0转移到标号show</code></li></ul><p>转移指令本身不影响任何标志位</p><h2 id="cmp指令"><a href="#cmp指令" class="headerlink" title="cmp指令"></a>cmp指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cmp 寄存器/内存 , 寄存器/内存/立即数</span><br></pre></td></tr></table></figure><p>cmp指令与sub指令相似,只是不保留计算结果,只设置标志位</p><p>常与条件转移指令结合使用:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cmp dh , 0</span><br><span class="line">jl</span><br></pre></td></tr></table></figure><p>JE、JZ:结果为零则跳转(相等时跳转)ZF=1<br>JNE、JNZ:结果不为零则跳转(不相等时跳转) ZF=0<br>JS :结果为负则跳转SF=1<br>JNS :结果为非负则跳转SF=0<br>JP, JPE:结果中1的个数为偶数则跳转PF=1<br>JNP, JPO:结果中1的个数为偶数则跳转PF=0<br>JO:结果溢出了则跳转OF=1<br>JNO:结果没有溢出则跳转OF=0<br>JB, JNAE:小于则跳转 (无符号数)CF=1<br>JNB, JAE:大于等于则跳转 (无符号数)CF=0<br>JBE, JNA:小于等于则跳转 (无符号数)CF=1 or ZF=1<br>JNBE, JA:大于则跳转(无符号数)CF=0 and ZF=0<br>JL, JNGE:小于则跳转 (有符号数)SF≠ OF<br>JNL, JGE:大于等于则跳转 (有符号数)SF=OF<br>JLE, JNG:小于等于则跳转 (有符号数)ZF=1 or SF≠ OF<br>JNLE, JG:大于则跳转(有符号数)ZF=0 and SF=OF</p><p><img src="/img/Snipaste_2023-08-01_17-25-58.png"></p>]]></content>
<summary type="html"><h1 id="计算机中的负数"><a href="#计算机中的负数" class="headerlink" title="计算机中的负数"></a>计算机中的负数</h1><h2 id="sub指令"><a href="#sub指令" class="headerlink" ti</summary>
<category term="汇编语言" scheme="http://example.com/tags/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/"/>
</entry>
<entry>
<title>循环批量传送和条件转移</title>
<link href="http://example.com/2023/07/31/2.%E5%BE%AA%E7%8E%AF%E6%89%B9%E9%87%8F%E4%BC%A0%E9%80%81%E5%92%8C%E6%9D%A1%E4%BB%B6%E8%BD%AC%E7%A7%BB/"/>
<id>http://example.com/2023/07/31/2.%E5%BE%AA%E7%8E%AF%E6%89%B9%E9%87%8F%E4%BC%A0%E9%80%81%E5%92%8C%E6%9D%A1%E4%BB%B6%E8%BD%AC%E7%A7%BB/</id>
<published>2023-07-30T16:00:00.000Z</published>
<updated>2023-07-31T08:43:26.354Z</updated>
<content type="html"><![CDATA[<h1 id="循环批量传送和条件转移"><a href="#循环批量传送和条件转移" class="headerlink" title="循环批量传送和条件转移"></a>循环批量传送和条件转移</h1><h2 id="串传送指令"><a href="#串传送指令" class="headerlink" title="串传送指令"></a>串传送指令</h2><p><strong>movsb 和 movsw</strong></p><p>传送前的准备工作:</p><p>设置元数据和目标数据位置</p><blockquote><p>DS:SI:原始数据串的段地址:偏移地址</p><p>ES:DI: 目标位置的短地址:偏移地址</p></blockquote><p>设置传送方向:</p><blockquote><p>通过设置flags寄存器第10位DF(direction flag)标志位,可以通过<code>cld</code>指令将DF标志位置为0,此时传送方向是从低地址到高地址(正向)。<code>std</code>指令将DF标志位置为1此时传送方向是从高地址到低地址(逆向)</p><p>在设置完方向后,每次执行movsb或movsw后对于的si di会指向下一待处理位置</p></blockquote><p>设置重复次数:</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rep 指令 ;rep=repeat重复</span><br></pre></td></tr></table></figure><p>重复对应的指令,重复的次数位于寄存器cx中,每次执行检测cx中的值,只有在cx不为0时才执行这条指令</p></blockquote><h2 id="和"><a href="#和" class="headerlink" title="$ 和 $$"></a>$ 和 $$</h2><p>$:当前指令的汇编地址</p><p>$$:当前所在段的起始的汇编地址</p><h2 id="loop指令"><a href="#loop指令" class="headerlink" title="loop指令"></a>loop指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">loop 标号</span><br></pre></td></tr></table></figure><p>loop指令的机器码:E2 8位相对偏移量</p><p>在8086中,loop指令的执行过程</p><blockquote><p>将寄存器 cx 的内容减一</p><p>如果 cx 的内容不为零,转移到指定的位置处执行,否则按顺序执行后面的指令</p></blockquote><h2 id="基址寻址"><a href="#基址寻址" class="headerlink" title="基址寻址"></a>基址寻址</h2><ul><li>寄存器BX在设计之初的作用之一就是用来提供数据访问的基地址,所以又叫基址寄存器(Base Address Register)</li><li>在设计8086cpu时,每个寄存器都有自己的太特殊用途,比如AX时累加器(Accumulator),与它有关的指令还会做指令长度的优化;CX是计数寄存器(count);DX是数据(Date)寄存器,除了作为通用寄存器使用外还专门用于和外设之间进行数据传送;SI是原索引寄存器(Source Index);DI是目标索引(Destination Index)寄存器,用于数据传送操作</li></ul><h2 id="inc-dec指令"><a href="#inc-dec指令" class="headerlink" title="inc dec指令"></a>inc dec指令</h2><p>inc = incream 递增</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">inc 寄存器/内存</span><br><span class="line">dec 寄存器/内存</span><br></pre></td></tr></table></figure><p>inc 用于将寄存器或内存地址中的值加一</p><p>dec 用于将寄存器或内存地址中的值减一</p><p>在8086中如果要用寄存器来提供偏移地址,只能使用 <strong>bx si di bp</strong>,不能采用其他寄存器</p><blockquote><p>mov [ax] , dl ;非法</p><p>mov [dx] , bl ;非法</p><p>mov word [bx] , 0x10 ;合法</p></blockquote><h2 id="基址变址寻址"><a href="#基址变址寻址" class="headerlink" title="基址变址寻址"></a>基址变址寻址</h2><p>基址寄存器bx bp</p><p>变址寄存器si di</p><p>在8086中只允许几种基址变址的组合</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">bx + si</span><br><span class="line">bx + di</span><br><span class="line">bp + si</span><br><span class="line">bp + di</span><br></pre></td></tr></table></figure><h2 id="jns指令"><a href="#jns指令" class="headerlink" title="jns指令"></a>jns指令</h2><p>当SF标志位为0时跳转,为1时不跳转继续执行下面指令</p><p>SF位为符号位,当运算结果最高位为0时SF为0,当运算结果最高位为1时SF为1</p><h2 id="用合理的方法显示数字"><a href="#用合理的方法显示数字" class="headerlink" title="用合理的方法显示数字"></a>用合理的方法显示数字</h2><p>使用循环:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"> jmp start</span><br><span class="line"></span><br><span class="line">mytest db 'L' , 0x07,'a',0x07,'b',0x07,'e',0x07,'l',0x07,' ',0x07,'o',0x07,</span><br><span class="line"> db 'f' , 0x07,'f',0x07,'s',0x07,'e',0x07,'t',0x07,':',0x07</span><br><span class="line"></span><br><span class="line">start:</span><br><span class="line"> ;---------------------------显示Labe offset:</span><br><span class="line"> mov ax , 0x7c0</span><br><span class="line"> mov ds , ax</span><br><span class="line"></span><br><span class="line"> mov ax , 0xb800</span><br><span class="line"> mov es , ax</span><br><span class="line"></span><br><span class="line"> cld ;将DF标志位清0表示方向为从低字节到高字节 </span><br><span class="line"> mov si , mytest</span><br><span class="line"> mov di , 0</span><br><span class="line"></span><br><span class="line"> mov cx , (start - mytest)/2 ;设置循环次数</span><br><span class="line"> rep movsw ;执行movsw cx内数据的次数</span><br><span class="line"> </span><br><span class="line"> ;-----------------------------------------------</span><br><span class="line"> mov ax , number ;得到标号的汇编地址</span><br><span class="line"></span><br><span class="line"> ;分解各个数位</span><br><span class="line"> mov bx , ax</span><br><span class="line"> mov cx , 5 ;循环次数</span><br><span class="line"> mov si , 10 ;除数</span><br><span class="line"></span><br><span class="line">dight:</span><br><span class="line"> xor dx , dx ;与ax一起形成被除数</span><br><span class="line"> div si</span><br><span class="line"> mov [bx] , dl ; 保存数位</span><br><span class="line"> inc bx</span><br><span class="line"> loop dight</span><br><span class="line"></span><br><span class="line"> mov cx , 5</span><br><span class="line"> ;显示各个数位</span><br><span class="line"> show:</span><br><span class="line"> dec bx ;取到对于的字符数字</span><br><span class="line"> mov al , [bx] </span><br><span class="line"> add al , 0x30 ;转换为对应的字符</span><br><span class="line"> mov ah , 04 ;对应的字符属性</span><br><span class="line"> mov [es:di] , ax ;es是显存对应的段地址,di是rep movsw后的di</span><br><span class="line"> add di , 2 ;找到写下一个字符的位置</span><br><span class="line"> loop show</span><br><span class="line"></span><br><span class="line"> jmp $</span><br><span class="line"></span><br><span class="line">number db 0,0,0,0,0</span><br><span class="line"></span><br><span class="line"> times 510-($-$$) db 0</span><br><span class="line"> db 0x55 , 0xaa</span><br></pre></td></tr></table></figure><p>使用跳转</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"> jmp start</span><br><span class="line"></span><br><span class="line">mytest db 'L' , 0x07,'a',0x07,'b',0x07,'e',0x07,'l',0x07,' ',0x07,'o',0x07, ; \是续行符编译器将其合并位一行</span><br><span class="line"> db 'f' , 0x07,'f',0x07,'s',0x07,'e',0x07,'t',0x07,':',0x07</span><br><span class="line"></span><br><span class="line">start:</span><br><span class="line"> ;---------------------------显示Labe offset:</span><br><span class="line"> mov ax , 0x7c0</span><br><span class="line"> mov ds , ax</span><br><span class="line"></span><br><span class="line"> mov ax , 0xb800</span><br><span class="line"> mov es , ax</span><br><span class="line"></span><br><span class="line"> cld ;将DF标志位清0表示方向为从低字节到高字节 </span><br><span class="line"> mov si , mytest</span><br><span class="line"> mov di , 0</span><br><span class="line"></span><br><span class="line"> mov cx , (start - mytest)/2 ;设置循环次数</span><br><span class="line"> rep movsw ;执行movsw cx内数据的次数</span><br><span class="line"> </span><br><span class="line"> ;-----------------------------------------------</span><br><span class="line"> mov ax , number ;得到标号的汇编地址</span><br><span class="line"></span><br><span class="line"> ;分解各个数位</span><br><span class="line"> mov bx , ax</span><br><span class="line"> mov cx , 5 ;循环次数</span><br><span class="line"> mov si , 10 ;除数</span><br><span class="line"></span><br><span class="line">dight:</span><br><span class="line"> xor dx , dx ;与ax一起形成被除数</span><br><span class="line"> div si</span><br><span class="line"> mov [bx] , dl ; 保存数位</span><br><span class="line"> inc bx</span><br><span class="line"> loop dight</span><br><span class="line"></span><br><span class="line"> mov bx , number</span><br><span class="line"> mov si , 4</span><br><span class="line"></span><br><span class="line">show:</span><br><span class="line"> mov al , [bx + si] ;基址和变址的组合</span><br><span class="line"> add al , 0x30</span><br><span class="line"> mov ah , 0x04</span><br><span class="line"> mov [es:di] , ax</span><br><span class="line"> add di , 2</span><br><span class="line"> dec si</span><br><span class="line"> jns show</span><br><span class="line"></span><br><span class="line"> jmp $</span><br><span class="line"></span><br><span class="line">number db 0,0,0,0,0</span><br><span class="line"></span><br><span class="line"> times 510-($-$$) db 0</span><br><span class="line"> db 0x55 , 0xaa</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="循环批量传送和条件转移"><a href="#循环批量传送和条件转移" class="headerlink" title="循环批量传送和条件转移"></a>循环批量传送和条件转移</h1><h2 id="串传送指令"><a href="#串传送指令" class</summary>
<category term="汇编语言" scheme="http://example.com/tags/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/"/>
</entry>
<entry>
<title>在屏幕上显示数字</title>
<link href="http://example.com/2023/07/30/1.%E5%B0%86%E6%95%B0%E5%AD%97%E6%89%93%E5%8D%B0%E5%88%B0%E5%B1%8F%E5%B9%95%E4%B8%8A/"/>
<id>http://example.com/2023/07/30/1.%E5%B0%86%E6%95%B0%E5%AD%97%E6%89%93%E5%8D%B0%E5%88%B0%E5%B1%8F%E5%B9%95%E4%B8%8A/</id>
<published>2023-07-29T16:00:00.000Z</published>
<updated>2023-07-30T15:14:13.146Z</updated>
<content type="html"><![CDATA[<h1 id="在屏幕上显示数字"><a href="#在屏幕上显示数字" class="headerlink" title="在屏幕上显示数字"></a>在屏幕上显示数字</h1><h2 id="div除法指令"><a href="#div除法指令" class="headerlink" title="div除法指令"></a>div除法指令</h2><p> 无符号除法指令,div</p><p> 指令:div 除数所在的寄存器或者内存地址</p><ul><li><p>如果在指令中指定的是8位寄存器或者8位操作数的内存地址,意味着被除数在寄存器AX中</p></li><li><p>相除后,商在寄存器AL里,余数在寄存器AH里</p></li><li><p>如果指令中指定的是16位寄存器或者16位操作数的内存地址,则意味着被除数是32位的,低16位在寄存器AX里,高16位在寄存器DX里</p></li><li><p>相除后,商在寄存器AX里,余数在寄存器DX里</p></li><li><p>如果指令中指定的是32位寄存器或者32位操作数的内存地址,则意味着被除数是64位的,低32位在寄存器EAX里,高32位在寄存器EDX里(80806不支持)</p></li><li><p>相除后,商在寄存器EAX里,余数在寄存器EDX里</p></li><li><p>如歌指令着指定的是64位寄存器或者64位操作数的内存地址,则意味着被除数是128位的,低64位在寄存器RAX中共,高64位在寄存器EDX里</p></li><li><p>相除后,商在寄存器RAX里,余数在寄存器RDX里</p></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">;计算378除37结果</span><br><span class="line">mov ax , 378</span><br><span class="line">mov bl , 37</span><br><span class="line">div bl ;al = 10(商) ah = 8(余数)</span><br></pre></td></tr></table></figure><h2 id="xor指令"><a href="#xor指令" class="headerlink" title="xor指令"></a>xor指令</h2><p>xor = exclusive or 异或</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xor 寄存器/内存 , 寄存器/内存/立即数</span><br></pre></td></tr></table></figure><p>计算结果保存在左操作数中,两操作数指定的数据长度必须相同</p><h2 id="add指令"><a href="#add指令" class="headerlink" title="add指令"></a>add指令</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">add 寄存器/内存地址 , 寄存器/捏成/立即数</span><br></pre></td></tr></table></figure><p>两个操作数的长度必须相同,而且<strong>两个操作数不可以同时为内存地址</strong></p><p>在屏幕上显示数字65535</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">start:</span><br><span class="line"> mov cx , 0 ;设置段地址为0</span><br><span class="line"> mov ds , cx</span><br><span class="line"> mov bx , 10 ; 65535 / 10</span><br><span class="line"></span><br><span class="line"> mov ax , 65535</span><br><span class="line"></span><br><span class="line"> xor dx , dx</span><br><span class="line"> div bx ; ax:6553 dx5</span><br><span class="line"> add dl , 0x30 ;将数字转换位对于的数字字符</span><br><span class="line"> mov [0x7c00 + buffer] , dl</span><br><span class="line"></span><br><span class="line"> xor dx , dx</span><br><span class="line"> div bx ;ax:655 dx:3</span><br><span class="line"> add dx , 0x30</span><br><span class="line"> mov [0x7c00 + buffer + 1] , dl</span><br><span class="line"></span><br><span class="line"> xor dx , dx</span><br><span class="line"> div bx ;ax:65 dx:5</span><br><span class="line"> add dx , 0x30</span><br><span class="line"> mov [0x7c00 + buffer + 2] , dl;</span><br><span class="line"></span><br><span class="line"> xor dx , dx</span><br><span class="line"> div bx ;ax:6 dx:5</span><br><span class="line"> add dl , 0x30</span><br><span class="line"> mov [0x7c00 + buffer + 3] , dl;</span><br><span class="line"> </span><br><span class="line"> xor dx , dx</span><br><span class="line"> div bx ;ax:0 dx:6</span><br><span class="line"> add dl , 0x30</span><br><span class="line"> mov [0x7c00 + buffer + 4] , dl;</span><br><span class="line"></span><br><span class="line">;-------输出到屏幕上---------------</span><br><span class="line"></span><br><span class="line"> mov cx , 0xb800</span><br><span class="line"> mov es , cx</span><br><span class="line"> </span><br><span class="line"> mov al , [0x7c00 + buffer + 4]</span><br><span class="line"> mov byte [es:0x00] , al ;使用段超越前缀指定内存访问的段寄存器</span><br><span class="line"> mov byte [es:0x01] , 0x2f</span><br><span class="line"></span><br><span class="line"> mov al , [0x7c00 + buffer + 3]</span><br><span class="line"> mov byte [es:0x02] , al ;使用段超越前缀指定内存访问的段寄存器</span><br><span class="line"> mov byte [es:0x03] , 0x2f</span><br><span class="line"></span><br><span class="line"> mov al , [0x7c00 + buffer + 2]</span><br><span class="line"> mov byte [es:0x04] , al ;使用段超越前缀指定内存访问的段寄存器</span><br><span class="line"> mov byte [es:0x05] , 0x2f</span><br><span class="line"></span><br><span class="line"> mov al , [0x7c00 + buffer + 1]</span><br><span class="line"> mov byte [es:0x06] , al ;使用段超越前缀指定内存访问的段寄存器</span><br><span class="line"> mov byte [es:0x07] , 0x2f</span><br><span class="line"></span><br><span class="line"> mov al , [0x7c00 + buffer + 0]</span><br><span class="line"> mov byte [es:0x08] , al ;使用段超越前缀指定内存访问的段寄存器</span><br><span class="line"> mov byte [es:0x09] , 0x2f</span><br><span class="line"></span><br><span class="line">again:</span><br><span class="line"> jmp again</span><br><span class="line">;--------------------------------------</span><br><span class="line"></span><br><span class="line">buffer db 0,0,0,0,0 ;标号buffer代表第一个字节0的地址</span><br><span class="line"></span><br><span class="line">current:</span><br><span class="line"> times 510-(current - start) db 0</span><br><span class="line"> db 0x55 , 0xaa</span><br></pre></td></tr></table></figure><ul><li><p>在NASM汇编中,标号可以由 字母 数字 _ $ # @ . ? 组成</p></li><li><p>其中可以打头的字符是 字母 . _ ?</p></li><li><p>冒号后面可以放一个冒号,但它不是标号的一部分</p></li><li><p>在需要两个操作数的指令中,如果至少有一个是寄存器。则不需要长度修饰符</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mov ah , bl</span><br><span class="line">mov [buffer] , ax</span><br><span class="line">xor byte [buffer] , 0x55</span><br></pre></td></tr></table></figure></blockquote></li><li><p>如果只有一个操作数且不是寄存器,必须使用长度修饰符</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">div word [divisor]</span><br></pre></td></tr></table></figure></blockquote></li><li><p>伪指令 db dw dd dq 分别用于定义 8 16 32 64 位的数据</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">db 0x55</span><br><span class="line">dw 0x55aa</span><br><span class="line">dd 0xabcd1234</span><br><span class="line">dq 0x12345678aabbccdd</span><br></pre></td></tr></table></figure></blockquote></li><li><p>伪指令times用来重复后面的指令若干次</p><blockquote><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">times 2 mov ax , bx</span><br><span class="line">等价于</span><br><span class="line">mov ax , bx</span><br><span class="line">mov ax, bx</span><br></pre></td></tr></table></figure></blockquote></li></ul>]]></content>
<summary type="html"><h1 id="在屏幕上显示数字"><a href="#在屏幕上显示数字" class="headerlink" title="在屏幕上显示数字"></a>在屏幕上显示数字</h1><h2 id="div除法指令"><a href="#div除法指令" class="header</summary>
<category term="汇编语言" scheme="http://example.com/tags/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/"/>
</entry>
<entry>
<title>变长模板</title>
<link href="http://example.com/2023/07/25/%E5%8F%98%E5%8F%82%E6%A8%A1%E6%9D%BF/"/>
<id>http://example.com/2023/07/25/%E5%8F%98%E5%8F%82%E6%A8%A1%E6%9D%BF/</id>
<published>2023-07-24T16:00:00.000Z</published>
<updated>2023-07-25T11:09:09.329Z</updated>
<content type="html"><![CDATA[<h1 id="变参模板"><a href="#变参模板" class="headerlink" title="变参模板"></a>变参模板</h1><h2 id="变参模板实列"><a href="#变参模板实列" class="headerlink" title="变参模板实列"></a>变参模板实列</h2><p>可以将模板参数定义成能够接受任意多个模板参数的情况。这一类模板被称为变参模板,可以通过调用下面代码中的 print()函数来打印一组数量和类型都不确定的参数:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">print</span><span class="params">()</span> </span>{}</span><br><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T , <span class="keyword">typename</span> ...Types></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">print</span><span class="params">(T firstArgs , Types... args)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> std::cout<<firstArgs<<<span class="string">'\n'</span>;</span><br><span class="line"> <span class="built_in">print</span>(args...);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当传入一个或多个参数时会调用模板函数,这里通过将第一个参数单独声明,就可以先打印第一个参数,然后再递归的调用<code>print()</code>,args被称为剩余参数,是一个函数参数包<code>Types ...args</code>,使用了模板参数包定义类型Types,为了结束递归,重载了不接受参数的非模板函数 print()</p><h2 id="变参和非变参模板的重载"><a href="#变参和非变参模板的重载" class="headerlink" title="变参和非变参模板的重载"></a>变参和非变参模板的重载</h2><p>上述print还可以通过如下方法实现:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> T></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">print</span><span class="params">(T arg)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> std::cout<<arg<<<span class="string">'\n'</span>;</span><br><span class="line">}</span><br><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T,<span class="keyword">typename</span> ...Types></span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">print</span><span class="params">(T firstArg , Types...args)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">print</span>(firstArg);</span><br><span class="line"> <span class="built_in">print</span>(args...);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当两个函数模板的区别只在于尾部的参数包的时候,会优先选择没有尾部参数包的那一个函数模板</p><h2 id="sizeof…-运算符"><a href="#sizeof…-运算符" class="headerlink" title="sizeof… 运算符"></a>sizeof… 运算符</h2><p>C++11 为变参模板引入了一种新的 sizeof 运算符:<code>sizeof...</code> 它会被扩展成参数包中所包含的参数数目,<code>sizeof...</code>既可以用于模板参数包也可以同于函数参数包,且二者效果相同(返回值相同),均返回当前参数包参数的个数</p><p>这样可能会让你觉得,可以不使用为了结束递归而重载的不接受参数的非模板函数 print(), 只要在没有参数的时候不去调用任何函数就可以了:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><<span class="keyword">typename</span> T, <span class="keyword">typename</span>… Types></span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">print</span> <span class="params">(T firstArg, Types… args)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">std::cout << firstArg << ’\n’;</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">sizeof</span>…(args) > <span class="number">0</span>) </span><br><span class="line">{ <span class="comment">//error if sizeof…(args)==0</span></span><br><span class="line"><span class="built_in">print</span>(args…); <span class="comment">// and no print() for no arguments declared</span></span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这种方法是错误的,,因为通常函数模板中 if 语句的两个分支都会被实例化。是否使用被实例化出来的代码是在运行期间(run-time)决定的,而是否实例化代码是在编译期间 (compile-time)决定的。因此如果在只有一个参数的时候调用 print()函数模板,虽然<code>args...</code>为空,if 语句中的 <code>print(args...)</code>也依然会被实例化,但此时没有定义不接受参数的 print()函数, 因此会报错</p><h2 id="变参下标"><a href="#变参下标" class="headerlink" title="变参下标"></a>变参下标</h2><p>下面的函数通过一组变参下标来访问第一个参数中相应的元素:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> C ,<span class="keyword">typename</span> ...Idx></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">printElems</span><span class="params">(C <span class="type">const</span> & coll , Idx... idx)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">print</span>(coll[idx]...);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> std::vector<std::string> coll = {<span class="string">"good"</span>,<span class="string">"times"</span>,<span class="string">"say"</span>} ;</span><br><span class="line"> <span class="built_in">printElems</span>(coll,<span class="number">2</span>,<span class="number">0</span>,<span class="number">1</span>); <span class="comment">//相当于 print(coll[2],coll[0],coll[1])</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>也可以将非类型模板参数声明成参数包</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">template</span><std::<span class="type">size_t</span>… Idx, typenam></span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">printIdx</span> <span class="params">(C <span class="type">const</span>& coll)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="built_in">print</span>(coll[Idx]…);</span><br><span class="line">}</span><br><span class="line">std::vector<std::string> coll = {<span class="string">"good"</span>, <span class="string">"times"</span>, <span class="string">"say"</span>, <span class="string">"bye"</span>};</span><br><span class="line"><span class="built_in">printIdx</span><<span class="number">2</span>,<span class="number">0</span>,<span class="number">3</span>>(coll);</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="变参模板"><a href="#变参模板" class="headerlink" title="变参模板"></a>变参模板</h1><h2 id="变参模板实列"><a href="#变参模板实列" class="headerlink" title="变参模板实</summary>
<category term="c++ 学习" scheme="http://example.com/tags/c-%E5%AD%A6%E4%B9%A0/"/>
</entry>
<entry>
<title>c++常见面试题</title>
<link href="http://example.com/2023/06/14/c++%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98/"/>
<id>http://example.com/2023/06/14/c++%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98/</id>
<published>2023-06-14T01:55:00.000Z</published>
<updated>2023-06-14T01:55:57.469Z</updated>
<content type="html"><![CDATA[<h1 id="c-常见面试题"><a href="#c-常见面试题" class="headerlink" title="c++常见面试题"></a>c++常见面试题</h1><ul><li>c++ this 指针干什么用的?</li></ul><p>一个类型定义了很多对象,对象之间拥有各自的成员变量,共享一套成员方法。this指针用来区分是那个对象的成员变量和那个对象调用了成员方法( 成员方法的第一个参数由编译器默认加上该对象的地址值(this指针) )</p><ul><li>c++的new和delete,什么时候用new[] 申请,可以用delete释放</li></ul><p>如果是自定义类型<strong>且提供了析构函数</strong>,会在对象数组前占用8字节用于记录对象的个数(强调:一定要有析构函数),那么使用<code>new[]</code>时一定要使用<code>delete[]</code>,当未提供析构函数时,类型为自定义类型,使用new[]开辟内存可以使用delete释放</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">A</span>() { <span class="built_in">printf</span>(<span class="string">"A() %#x \n"</span>,<span class="keyword">this</span>); }</span><br><span class="line"> <span class="comment">//~A() {}</span></span><br><span class="line"> <span class="type">int</span> a = <span class="number">0</span>;</span><br><span class="line"> <span class="type">int</span> b = <span class="number">0</span>;</span><br><span class="line"> <span class="type">double</span> c = <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line">执行:A * pa = <span class="keyword">new</span> A[<span class="number">10</span>];</span><br></pre></td></tr></table></figure><p>当不存在析构函数时,pa所指内存及前8字节</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fd fd fd fd <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> 后全为<span class="number">0</span></span><br></pre></td></tr></table></figure><p>当存在析构函数时,pa所指内存及前8字节</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">0</span>a <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> 后全为<span class="number">0</span></span><br></pre></td></tr></table></figure><ul><li>c++ static关键字作用</li></ul><p>static修饰全局变量,函数时将其作用域变为当前文件可见,在符号表中符号的作用域就从 global 变为 local</p><p>static修饰局部变量(一般为函数内的静态变量)在第一次使用时分配内存并初始化。这里的变量包含内置数据类型和自定义类型的对象,位于 .bss段 或 .data段,符号作用域为 local</p><p>static修饰类成员变量,是的该变量为类中公有可以通过类直接访问。修饰类成员方法,可以直接通过类访问而不需要对象</p><ul><li>c++如何防止内存泄漏</li></ul><p>内存泄漏是指分配的内存没有释放,也没有机会释放,如忘记,或在释放之前抛异常。解决方式是使用智能指针</p><ul><li>STL中迭代器失效问题</li></ul><p>迭代器不允许一边读一边写。当通过迭代器插入一个元素,所有迭代器失效。当通过迭代器删除一个元素时,当前删除位置到后面所有元素的迭代器就都失效了。</p><p>当通过迭代器更新容器元素以后,要及时对迭代器进行更新,insert / erase 方法都会返回新位置的迭代器</p><ul><li>构造函数和析构函数能不能是虚函数</li></ul><p>构造函数不能是虚函数,对象在构造函数之后产生,没有对象就没有 vfptr</p><p>析构函数可以,当基类中拥有虚函数时,必须其析构函数必须为析构函数</p><ul><li>宏和内联函数的区别</li></ul><p>#define 的处理时机是在预处理 inline 的处理时机是在编译阶段 , inline在函数调用点展开,通过函数的实参把函数代码直接展开调用,节省了函数调用栈的开销,但inline只是一种建议</p><ul><li>拷贝构造为什么传引用不传值</li></ul><p>若存在拷贝构造为 :<code>A &operator=(const A other)</code> 当此函数被调用时,先发生拷贝构造,用于构造 A other,然后调用operator=。若使用引用,则避免了拷贝构造带来的开销</p><ul><li>如何实现一个不可以被继承的类</li></ul><p>派生类的构造过程为:基类构造->派生类构造,故只需可将基类构造函数私有化</p><p>c++11 中引入final 用于阻止类的继承和虚函数的重写</p><ul><li>什么时纯虚函数,为什么要有纯虚函数,虚函数表放在那里</li></ul><p>virtual void func() = 0; func就为纯虚函数,拥有纯虚函数的类称为抽象类(不能定义对象,但可以定义指针或引用)</p><p>纯虚函数一般定义在基类中,基类不代表任意实体,它主要的作用之一是给所有的派生类保留统一的接口,方便使用多态,基类也不需要实例化</p><p>虚函数表位于 .rodata 段</p><ul><li>c++中的const 以及和 static 的区别</li></ul><p>const修饰类型具有只读属性。const 定义的被称为常量,当用真正的常量初始化时在编译过程中,把出现名字相同的值进行替换,当用变量初始化时不会发生变化</p><p>const还可以修饰成员方法,其 this 指针所指对象将具有const属性,这样普通对象和常对象就都可以调用了,但是无法修改器成员变量</p><ul><li>deque的底层原理</li></ul><p>底层是一个动态开辟的二维数组,存在两个宏定义:<code>#define MAP_SIZE 2#define QUE_SIZE(T) 4096/sizeof(T)</code> ,其中MAP_SIZE是一维数组的个数,QUE_SIZE 是二维数组的大小</p><ul><li>异常机制是怎么回事</li></ul><p>try {可能抛出异常的代码} catch(类型){捕获相应异常类型对象,进行处理,完成后代码继续运行},处理过程是栈展开,首先查看当前函数栈中是否存在对应的catch若有则处理,没有则销毁当前函数栈,向调用方抛出异常</p><ul><li>早绑定和晚绑定</li></ul><p>早绑定:普通函数的绑定,对象调用虚函数</p><p>晚绑定:用指针或引用调用虚函数</p><ul><li>智能指针交叉引用问题怎么解决</li></ul><p>在定义对象时使用强智能指针 share_ptr 引用对象时使用弱智能指针 weak_ptr</p>]]></content>
<summary type="html"><h1 id="c-常见面试题"><a href="#c-常见面试题" class="headerlink" title="c++常见面试题"></a>c++常见面试题</h1><ul>
<li>c++ this 指针干什么用的?</li>
</ul>
<p>一个类型定义了很多对</summary>
<category term="c++ 学习" scheme="http://example.com/tags/c-%E5%AD%A6%E4%B9%A0/"/>
</entry>
<entry>
<title>常用设计模式</title>
<link href="http://example.com/2023/06/09/%E5%B8%B8%E7%94%A8%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
<id>http://example.com/2023/06/09/%E5%B8%B8%E7%94%A8%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/</id>
<published>2023-06-09T08:25:00.000Z</published>
<updated>2023-06-09T08:34:24.354Z</updated>
<content type="html"><![CDATA[<h1 id="常用设计模式"><a href="#常用设计模式" class="headerlink" title="常用设计模式"></a>常用设计模式</h1><h2 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h2><p>定义:<strong>一个类不管创建多少次对象,永远只能得到该类型一个对象的实例</strong>,常用到的如,日志模块,数据库模块</p><p>分类:</p><ul><li>饿汉式单例模式:还没有获取实例对象,实例对象就已经产生了</li><li>懒汉式单例模式:唯一的实例对象直到第一次获取时才创建</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//饿汉式单例:</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Singleton</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">static</span> Singleton * <span class="title">getInstance</span><span class="params">()</span><span class="comment">//#3 定义静态成员方法(不需要通过对象调用),获取类唯一实例对象的接口方法</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">return</span> &instance;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="type">static</span> Singleton instance; <span class="comment">// #2 定义一个唯一的类的实例对象,位于数据段</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">Singleton</span>() <span class="comment">// #1 构造函数私有化</span></span><br><span class="line"> {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Singleton</span>(<span class="type">const</span> Singleton& ) = <span class="keyword">delete</span>;</span><br><span class="line"> Singleton& <span class="keyword">operator</span>=(<span class="type">const</span> Singleton&) = <span class="keyword">delete</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">Singleton Singleton::instance; <span class="comment">//静态成员变量类内声明类外定义</span></span><br></pre></td></tr></table></figure><p>单例的饿汉实现是线程安全的,因为对象在使用前就已经创建出来了。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//懒汉式单例:</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Singleton</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">static</span> Singleton * <span class="title">getInstance</span><span class="params">()</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">if</span>(instance == <span class="literal">nullptr</span>) {</span><br><span class="line"> instance = <span class="keyword">new</span> <span class="built_in">Singleton</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="type">static</span> Singleton* instance;</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Singleton</span>() </span><br><span class="line"> {</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">Singleton</span>(<span class="type">const</span> Singleton& ) = <span class="keyword">delete</span>;</span><br><span class="line"> Singleton& <span class="keyword">operator</span>=(<span class="type">const</span> Singleton&) = <span class="keyword">delete</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">Singleton* Singleton::instance = <span class="literal">nullptr</span>;</span><br><span class="line"><span class="comment">//未调用getInstance时,instance一直为nullptr</span></span><br><span class="line"><span class="comment">//当调用getInstance时,生成唯一对象并为instance赋值</span></span><br></pre></td></tr></table></figure><p>上述懒汉式单例模式并非线程安全,getInstance函数并非可重入函数。若要改为线程安全的懒汉式单例模式只需修改getInstance函数</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> Singleton * <span class="title">getInstance</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span>(instance == <span class="literal">nullptr</span>) {</span><br><span class="line"> <span class="function">std::lock_guard<std::mutex> <span class="title">guard</span><span class="params">(mtx)</span></span>; <span class="comment">//保证只能实例化一个对象</span></span><br><span class="line"> <span class="keyword">if</span>(instance == <span class="literal">nullptr</span>) {</span><br><span class="line"> instance = <span class="keyword">new</span> <span class="built_in">Singleton</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line">}</span><br><span class="line"><span class="type">static</span> Singleton* <span class="keyword">volatile</span> instance; <span class="comment">//防止线程缓冲影响</span></span><br></pre></td></tr></table></figure><p>全局变量、文件域的静态变量和类的静态成员变量在main执行之前的静态初始化过程中分配内存并初始化;<strong>局部静态变量(一般为函数内的静态变量)在第一次使用时分配内存并初始化。这里的变量包含内置数据类型和自定义类型的对象</strong>。</p><p>非局部静态变量一般在main执行之前的静态初始化过程中分配内存并初始化,可以认为是线程安全的;</p><p>局部静态变量在编译时,在g++中通过查看汇编指令发现已经添加了线程互斥操作,故是线程安全的</p><p>故懒汉式单例模式也可以改写为:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Singleton</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">static</span> Singleton * <span class="title">getInstance</span><span class="params">()</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="type">static</span> Singleton instance;</span><br><span class="line"> <span class="keyword">return</span> &instance;</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> <span class="type">static</span> Singleton* <span class="keyword">volatile</span> instance;</span><br><span class="line"> <span class="built_in">Singleton</span>() </span><br><span class="line"> {</span><br><span class="line"> }</span><br><span class="line"> <span class="built_in">Singleton</span>(<span class="type">const</span> Singleton& ) = <span class="keyword">delete</span>;</span><br><span class="line"> Singleton& <span class="keyword">operator</span>=(<span class="type">const</span> Singleton&) = <span class="keyword">delete</span>;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><h2 id="工厂模式"><a href="#工厂模式" class="headerlink" title="工厂模式"></a>工厂模式</h2><p>定义:</p><p>这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。简单来说,使用了C++<strong>多态</strong>的特性,将存在<strong>继承</strong>关系的类,通过一个工厂类创建对应的子类(派生类)对象。在项目复杂的情况下,可以便于子类对象的创建。</p><p>分类:</p><ul><li>简单工厂:simple factory</li><li>工厂方法:factory method</li><li>抽象工厂:abstract factory</li></ul><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//简单工厂</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Car</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">Car</span><span class="params">(string name)</span></span></span><br><span class="line"><span class="function"> : _name(std::move(name)) {</span>}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">show</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> string _name;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BMW</span> : <span class="keyword">public</span> Car {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">BMW</span><span class="params">(string name)</span></span></span><br><span class="line"><span class="function"> : Car(std::move(name)) {</span>}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> cout << <span class="string">"获取BMW"</span> << _name << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Audi</span> : <span class="keyword">public</span> Car {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">Audi</span><span class="params">(string name)</span></span></span><br><span class="line"><span class="function"> : Car(std::move(name)) {</span>}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> cout << <span class="string">"获取AUdi"</span> << _name << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">enum class</span> <span class="title class_">CarType</span> {</span><br><span class="line"> BMW, AUDI</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">SimpalFactory</span> { <span class="comment">//工厂类</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">Car *<span class="title">createCar</span><span class="params">(CarType ct)</span> </span>{</span><br><span class="line"> <span class="keyword">switch</span> (ct) {</span><br><span class="line"> <span class="keyword">case</span> CarType::BMW:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">BMW</span>(<span class="string">"X1"</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> CarType::AUDI:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Audi</span>(<span class="string">"A6"</span>);</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> cerr << <span class="string">"Usage argv is not right"</span> << endl;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> SimpalFactory factory;</span></span><br><span class="line"><span class="comment"> Car *bmw = factory.createCar(CarType::BMW);</span></span><br><span class="line"><span class="comment"> Car *audi = factory.createCar(CarType::AUDI);</span></span><br><span class="line"><span class="comment"> bmw->show();delete bmw;</span></span><br><span class="line"><span class="comment"> audi->show();delete audi;</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"> <span class="function">unique_ptr<SimpalFactory> <span class="title">factory</span><span class="params">(<span class="keyword">new</span> SimpalFactory())</span></span>;</span><br><span class="line"> <span class="function">unique_ptr<Car> <span class="title">p1</span><span class="params">(factory->createCar(CarType::BMW))</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>简单工厂模式在扩展时要修改许多东西,当通过多态增加一种汽车时,工厂类对应的枚举值要增加,要修改工厂类中createCar函数。比较麻烦不符合对扩展关闭的原则,改进为工厂方法:</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//工厂方法:</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Car</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">Car</span><span class="params">(string name)</span></span></span><br><span class="line"><span class="function"> : _name(std::move(name)) {</span>}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">show</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> string _name;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BMW</span> : <span class="keyword">public</span> Car {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">BMW</span><span class="params">(string name)</span></span></span><br><span class="line"><span class="function"> : Car(std::move(name)) {</span>}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> cout << <span class="string">"获取BMW"</span> << _name << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Audi</span> : <span class="keyword">public</span> Car {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">Audi</span><span class="params">(string name)</span></span></span><br><span class="line"><span class="function"> : Car(std::move(name)) {</span>}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> cout << <span class="string">"获取AUdi"</span> << _name << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Factory</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> Car *<span class="title">createCar</span><span class="params">(string name)</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BMWFactory</span> : <span class="keyword">public</span> Factory {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">Car *<span class="title">createCar</span><span class="params">(string name)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">BMW</span>(name);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AudiFactory</span> : <span class="keyword">public</span> Factory { </span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">Car * <span class="title">createCar</span><span class="params">(string name)</span> <span class="keyword">override</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Audi</span>(name);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="function">unique_ptr<Factory> <span class="title">bmwfty</span><span class="params">(<span class="keyword">new</span> BMWFactory())</span></span>;</span><br><span class="line"> <span class="function">unique_ptr<Factory> <span class="title">audifty</span><span class="params">(<span class="keyword">new</span> AudiFactory())</span></span>;</span><br><span class="line"> <span class="function">unique_ptr<Car> <span class="title">p1</span><span class="params">(bmwfty->createCar(<span class="string">"X6"</span>))</span></span>;</span><br><span class="line"> <span class="function">unique_ptr<Car> <span class="title">p2</span><span class="params">(audifty->createCar(<span class="string">"A6"</span>))</span></span>;</span><br><span class="line"> p1-><span class="built_in">show</span>();</span><br><span class="line"> p2-><span class="built_in">show</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>对于工厂方法,当扩展时,只需继承Car通过多态实现实体后,继承Factory通过多态实现createCar。</p><p>但是实际情况并非如此,一个工厂可能及生产手机又生产耳机或者生产音响,对于工厂方法来说,每个工厂只能生产一个物品,当物品多且相似时这种设计是不好的,因此产生了抽象工厂</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//抽象工厂</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Car</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">Car</span><span class="params">(string name)</span></span></span><br><span class="line"><span class="function"> : _name(std::move(name)) {</span>}</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">show</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line"> string _name;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BMW</span> : <span class="keyword">public</span> Car {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">BMW</span><span class="params">(string name)</span></span></span><br><span class="line"><span class="function"> : Car(std::move(name)) {</span>}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> <span class="keyword">override</span> </span>{ cout << <span class="string">"获取BMW"</span> << _name << endl; }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Audi</span> : <span class="keyword">public</span> Car {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">explicit</span> <span class="title">Audi</span><span class="params">(string name)</span></span></span><br><span class="line"><span class="function"> : Car(std::move(name)) {</span>}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> <span class="keyword">override</span> </span>{ cout << <span class="string">"获取AUdi"</span> << _name << endl; }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Light</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">show</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">bmwLight</span> : <span class="keyword">public</span> Light {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> <span class="keyword">override</span> </span>{ cout << <span class="string">"BMW light"</span> << endl; }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">audiLight</span> : <span class="keyword">public</span> Light {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> <span class="keyword">override</span> </span>{ cout << <span class="string">"Audi light"</span> << endl; }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">//抽象方法</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AbstractFactory</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> Car *<span class="title">createCar</span><span class="params">(string name)</span> </span>= <span class="number">0</span>; <span class="comment">//创建汽车</span></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> Light *<span class="title">createCarLight</span><span class="params">()</span> </span>= <span class="number">0</span>; <span class="comment">//创建灯</span></span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BMWFactory</span> : <span class="keyword">public</span> AbstractFactory {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">Car *<span class="title">createCar</span><span class="params">(string name)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">BMW</span>(name);</span><br><span class="line"> }</span><br><span class="line"> <span class="function">Light *<span class="title">createCarLight</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> bmwLight;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AudiFactory</span> : <span class="keyword">public</span> AbstractFactory {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function">Car *<span class="title">createCar</span><span class="params">(string name)</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Audi</span>(name);</span><br><span class="line"> }</span><br><span class="line"> <span class="function">Light *<span class="title">createCarLight</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> audiLight;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="function">unique_ptr<AbstractFactory> <span class="title">bmwfty</span><span class="params">(<span class="keyword">new</span> BMWFactory)</span></span>;</span><br><span class="line"> <span class="function">unique_ptr<AbstractFactory> <span class="title">Audifty</span><span class="params">(<span class="keyword">new</span> AudiFactory)</span></span>;</span><br><span class="line"> <span class="function">unique_ptr<Car> <span class="title">p1</span><span class="params">(bmwfty->createCar(<span class="string">"x1"</span>))</span></span>;</span><br><span class="line"> <span class="function">unique_ptr<Car> <span class="title">p2</span><span class="params">(Audifty->createCar(<span class="string">"A6"</span>))</span></span>;</span><br><span class="line"> <span class="function">unique_ptr<Light> <span class="title">p3</span><span class="params">(bmwfty->createCarLight())</span></span>;</span><br><span class="line"> <span class="function">unique_ptr<Light> <span class="title">p4</span><span class="params">(Audifty->createCarLight())</span></span>;</span><br><span class="line"> p1-><span class="built_in">show</span>();p2-><span class="built_in">show</span>();p3-><span class="built_in">show</span>();p4-><span class="built_in">show</span>();</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>但是抽象工厂也存在一些问题,当bmw工厂存在特殊的方法(其他工厂没有时),此时需要在Abstractory基类中添加纯虚函数,但是只有bmw工厂给出了具体的实现,其他工厂必须重写否则无法通过编译,一般实现为空</p><blockquote><p>简单工厂把对象的创建封装在一个接口函数里面,通过传入不同的标志,返回创建的对象,客户不用自己new对象,不用了解对象创建的细节</p><p>工厂方法Factory基类提供了一个纯虚函数(创造产品),定义派生类(具体产品的工厂)负责创建对应的产品,可以做到不同的产品在不同的工厂里创建</p><p>抽象工厂把有关联的属于一个产品族的所有产品创建接口函数,放在一个抽象工厂中,派生类(具体产品的工厂)应该负责创建该产品族里的所有产品</p></blockquote><h2 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h2><p>通过代理类来控制实际对象的访问权限</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">VideoSite</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">freeMovie</span><span class="params">()</span> </span>= <span class="number">0</span>; <span class="comment">//免费电影</span></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">vipMovie</span><span class="params">()</span> </span>= <span class="number">0</span>; <span class="comment">//vip电影</span></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">ticketMovie</span><span class="params">()</span> </span>= <span class="number">0</span>; <span class="comment">//用卷购买的电影</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FixbugVideoSite</span> : <span class="keyword">public</span> VideoSite { <span class="comment">//FixbugVideoSite 中存在所有的实现</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">freeMovie</span><span class="params">()</span> </span>{</span><br><span class="line"> cout << <span class="string">"freeMovie"</span> << endl;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">vipMovie</span><span class="params">()</span> </span>{</span><br><span class="line"> cout << <span class="string">"vipMovie"</span> << endl;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">ticketMovie</span><span class="params">()</span> </span>{</span><br><span class="line"> cout << <span class="string">"ticketMovie"</span> << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">FreeVideoSiteProxy</span> : <span class="keyword">public</span> VideoSite { <span class="comment">//普通用户代理类</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">FreeVideoSiteProxy</span>() { pVideo = <span class="keyword">new</span> FixbugVideoSite; } <span class="comment">//指向实现类</span></span><br><span class="line"> ~<span class="built_in">FreeVideoSiteProxy</span>() { <span class="keyword">delete</span> pVideo; }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">freeMovie</span><span class="params">()</span></span>{ <span class="comment">//实现访问控制</span></span><br><span class="line"> pVideo-><span class="built_in">freeMovie</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">vipMovie</span><span class="params">()</span> </span>{</span><br><span class="line"> cout<<<span class="string">"vip视频,您无权访问"</span><<endl;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">ticketMovie</span><span class="params">()</span></span>{</span><br><span class="line"> cout<<<span class="string">"需要买卷"</span><<endl;</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> VideoSite *pVideo; <span class="comment">//存有基类指针</span></span><br><span class="line"> <span class="comment">//FixbugVideoSite video 通过组合的方式也可以,但是这这能代理FixbugVideoSite类,降低了灵活性,</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">vipVideoSiteProxy</span> : <span class="keyword">public</span> VideoSite{ <span class="comment">//vip用户代理类</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">vipVideoSiteProxy</span>(){ p = <span class="keyword">new</span> FixbugVideoSite ;}</span><br><span class="line"> ~<span class="built_in">vipVideoSiteProxy</span>() { <span class="keyword">delete</span> p; }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">freeMovie</span><span class="params">()</span></span>{ <span class="comment">//实现访问控制</span></span><br><span class="line"> p-><span class="built_in">freeMovie</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">vipMovie</span><span class="params">()</span> </span>{</span><br><span class="line"> p-><span class="built_in">vipMovie</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">ticketMovie</span><span class="params">()</span></span>{</span><br><span class="line"> cout<<<span class="string">"需要买卷"</span><<endl;</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> VideoSite* p;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">watchMovie</span><span class="params">(unique_ptr<VideoSite> & ptr)</span></span>{</span><br><span class="line"> ptr-><span class="built_in">freeMovie</span>();</span><br><span class="line"> ptr-><span class="built_in">vipMovie</span>();</span><br><span class="line"> ptr-><span class="built_in">ticketMovie</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function">unique_ptr<VideoSite> <span class="title">p1</span><span class="params">(<span class="keyword">new</span> FreeVideoSiteProxy)</span></span>; <span class="comment">//直接访问代理对象,而非实际对象</span></span><br><span class="line"> <span class="function">unique_ptr<VideoSite> <span class="title">p2</span><span class="params">(<span class="keyword">new</span> vipVideoSiteProxy)</span></span>;</span><br><span class="line"> <span class="comment">//p1->freeMovie(); p1->vipMovie(); p1->ticketMovie();</span></span><br><span class="line"> <span class="comment">//p2->freeMovie(); p2->vipMovie(); p2->ticketMovie();</span></span><br><span class="line"> <span class="built_in">watchMovie</span>(p1);</span><br><span class="line"> <span class="built_in">watchMovie</span>(p2);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="装饰器模式"><a href="#装饰器模式" class="headerlink" title="装饰器模式"></a>装饰器模式</h2><p>装饰器模式是为了增加现有类的功能。但是增加现有类功能的另一个方法是增加一个子类</p> <figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Car</span> { <span class="comment">//抽象基类</span></span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">show</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line"><span class="comment">//三个实体的派生类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">BMW</span> : <span class="keyword">public</span> Car {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> </span>{</span><br><span class="line"> cout << <span class="string">"这是一个BMW汽车,配置有:基本配置"</span> << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Audi</span> : <span class="keyword">public</span> Car {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> </span>{</span><br><span class="line"> cout << <span class="string">"这是一个Audi汽车,配置有:基本配置"</span> << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Benz</span> : <span class="keyword">public</span> Car {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span> </span>{</span><br><span class="line"> cout << <span class="string">"这是一个Benz汽车,配置有:基本配置"</span> << endl;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConcreteDecorator01</span> : <span class="keyword">public</span> Car</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">ConcreteDecorator01</span>(Car* p) : <span class="built_in">pCar</span>(p) {}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> pCar-><span class="built_in">show</span>();</span><br><span class="line"> cout<<<span class="string">"新增方法01"</span><<endl;</span><br><span class="line"> }</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> Car* pCar;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConcreteDecorator02</span> : <span class="keyword">public</span> Car</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">ConcreteDecorator02</span>(Car* p) : <span class="built_in">pCar</span>(p) {}</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">show</span><span class="params">()</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> pCar-><span class="built_in">show</span>();</span><br><span class="line"> cout<<<span class="string">"新增方法02"</span><<endl;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> Car * pCar;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> Car * p1 = <span class="keyword">new</span> <span class="built_in">ConcreteDecorator01</span>(<span class="keyword">new</span> <span class="built_in">BMW</span>()); <span class="comment">//对具有普通功能进行装饰,使其新增功能1</span></span><br><span class="line"> p1-><span class="built_in">show</span>();</span><br><span class="line"> Car * p2 = <span class="keyword">new</span> <span class="built_in">ConcreteDecorator01</span>(<span class="keyword">new</span> <span class="built_in">ConcreteDecorator02</span>(<span class="keyword">new</span> BMW));</span><br><span class="line"> p2-><span class="built_in">show</span>();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="适配器模式"><a href="#适配器模式" class="headerlink" title="适配器模式"></a>适配器模式</h2><p>让不兼容的接口可以在一起工作</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">VGA</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">play</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line"> <span class="function">std::string <span class="title">getType</span><span class="params">()</span> </span>{ <span class="keyword">return</span> <span class="string">"VGA"</span>; }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TV01</span> : <span class="keyword">public</span> VGA {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">play</span><span class="params">()</span> </span>{ cout << <span class="string">"通过VGA 接口连接投影 并播放"</span> << endl; }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Computer</span> <span class="comment">//只支持VGA接口的电脑类</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">PlayVideo</span><span class="params">(VGA *pVGA)</span> <span class="comment">//由于只支持VGA接口,故参数只能是VGA的指针或引用</span></span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> pVGA-><span class="built_in">play</span>();</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">HDMI</span> {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">play</span><span class="params">()</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TV02</span> : <span class="keyword">public</span> HDMI {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">play</span><span class="params">()</span> <span class="keyword">override</span> </span>{ cout << <span class="string">"通过HDMI 接口连接投影 并播放"</span> << endl; }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">//由于电脑(VGA)投影仪(HDMI)无法直接相连</span></span><br><span class="line"><span class="comment">// 需要使用适配器,将 VGA 信号转换为 HDMI 信号</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">VGA2HDMI</span> : <span class="keyword">public</span> VGA {</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"> <span class="built_in">VGA2HDMI</span>(HDMI *p) : <span class="built_in">pHDMI</span>(p) {}</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="type">void</span> <span class="title">play</span><span class="params">()</span> <span class="keyword">override</span> </span>{</span><br><span class="line"> pHDMI-><span class="built_in">play</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line"> HDMI *pHDMI;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>{</span><br><span class="line"> Computer c;</span><br><span class="line"> c.<span class="built_in">PlayVideo</span>(<span class="keyword">new</span> TV01);</span><br><span class="line"> c.<span class="built_in">PlayVideo</span>(<span class="keyword">new</span> <span class="built_in">VGA2HDMI</span>(<span class="keyword">new</span> <span class="built_in">TV02</span>()));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="观察者模式"><a href="#观察者模式" class="headerlink" title="观察者模式"></a>观察者模式</h2><p>主要关注的是对象的一对多关系,也就是多个对象都依赖一个对象,当该对象的状态发生改变时,其他对象都能接受到相应的通知</p><p>如:我们有一组数据,可以通过这组数据生成对应的曲线图,柱状图,圆饼图。当数据改变时,这三个图都要发生变化</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 观察者抽象类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Observer</span> </span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="comment">// 处理消息的接口</span></span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">handle</span><span class="params">(<span class="type">int</span> msgid)</span> </span>= <span class="number">0</span>;</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 第一个观察者实例</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Observer1</span> : <span class="keyword">public</span> Observer</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">handle</span><span class="params">(<span class="type">int</span> msgid)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">switch</span> (msgid)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">cout << <span class="string">"Observer1 recv 1 msg!"</span> << endl;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">cout << <span class="string">"Observer1 recv 2 msg!"</span> << endl;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">cout << <span class="string">"Observer1 recv unknow msg!"</span> << endl;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 第二个观察者实例</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Observer2</span> : <span class="keyword">public</span> Observer</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">handle</span><span class="params">(<span class="type">int</span> msgid)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">switch</span> (msgid)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line">cout << <span class="string">"Observer2 recv 2 msg!"</span> << endl;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">cout << <span class="string">"Observer2 recv unknow msg!"</span> << endl;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">};</span><br><span class="line"><span class="comment">// 第三个观察者实例</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Observer3</span> : <span class="keyword">public</span> Observer</span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">handle</span><span class="params">(<span class="type">int</span> msgid)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">switch</span> (msgid)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">cout << <span class="string">"Observer3 recv 1 msg!"</span> << endl;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line">cout << <span class="string">"Observer3 recv 3 msg!"</span> << endl;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">default</span>:</span><br><span class="line">cout << <span class="string">"Observer3 recv unknow msg!"</span> << endl;</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 主题类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Subject</span></span><br><span class="line">{</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line"><span class="comment">// 给主题增加观察者对象</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">addObserver</span><span class="params">(Observer* obser, <span class="type">int</span> msgid)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">_subMap[msgid].<span class="built_in">push_back</span>(obser);</span><br><span class="line"><span class="comment">/*auto it = _subMap.find(msgid);</span></span><br><span class="line"><span class="comment">if (it != _subMap.end())</span></span><br><span class="line"><span class="comment">{</span></span><br><span class="line"><span class="comment">it->second.push_back(obser);</span></span><br><span class="line"><span class="comment">}</span></span><br><span class="line"><span class="comment">else</span></span><br><span class="line"><span class="comment">{</span></span><br><span class="line"><span class="comment">list<Observer*> lis;</span></span><br><span class="line"><span class="comment">lis.push_back(obser);</span></span><br><span class="line"><span class="comment">_subMap.insert({ msgid, lis });</span></span><br><span class="line"><span class="comment">}*/</span></span><br><span class="line">}</span><br><span class="line"><span class="comment">// 主题检测发生改变,通知相应的观察者对象处理事件</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">dispatch</span><span class="params">(<span class="type">int</span> msgid)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"><span class="keyword">auto</span> it = _subMap.<span class="built_in">find</span>(msgid);</span><br><span class="line"><span class="keyword">if</span> (it != _subMap.<span class="built_in">end</span>())</span><br><span class="line">{</span><br><span class="line"><span class="keyword">for</span> (Observer *pObser : it->second)</span><br><span class="line">{</span><br><span class="line">pObser-><span class="built_in">handle</span>(msgid);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">unordered_map<<span class="type">int</span>, list<Observer*>> _subMap;</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">Subject subject;</span><br><span class="line">Observer *p1 = <span class="keyword">new</span> <span class="built_in">Observer1</span>();</span><br><span class="line">Observer *p2 = <span class="keyword">new</span> <span class="built_in">Observer2</span>();</span><br><span class="line">Observer *p3 = <span class="keyword">new</span> <span class="built_in">Observer3</span>();</span><br><span class="line"></span><br><span class="line">subject.<span class="built_in">addObserver</span>(p1, <span class="number">1</span>);</span><br><span class="line">subject.<span class="built_in">addObserver</span>(p1, <span class="number">2</span>);</span><br><span class="line">subject.<span class="built_in">addObserver</span>(p2, <span class="number">2</span>);</span><br><span class="line">subject.<span class="built_in">addObserver</span>(p3, <span class="number">1</span>);</span><br><span class="line">subject.<span class="built_in">addObserver</span>(p3, <span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> msgid = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">for</span> (;;)</span><br><span class="line">{</span><br><span class="line">cout << <span class="string">"输入消息id:"</span>;</span><br><span class="line">cin >> msgid;</span><br><span class="line"><span class="keyword">if</span> (msgid == <span class="number">-1</span>)</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">subject.<span class="built_in">dispatch</span>(msgid);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html"><h1 id="常用设计模式"><a href="#常用设计模式" class="headerlink" title="常用设计模式"></a>常用设计模式</h1><h2 id="单例模式"><a href="#单例模式" class="headerlink" title="单</summary>
<category term="c++ 学习" scheme="http://example.com/tags/c-%E5%AD%A6%E4%B9%A0/"/>
</entry>
</feed>