-
Notifications
You must be signed in to change notification settings - Fork 0
/
local-search.xml
2463 lines (1183 loc) · 888 KB
/
local-search.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
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>标准 Linux 启用 ZeroTier 局域网转发</title>
<link href="/p/d556ca0e.html"/>
<url>/p/d556ca0e.html</url>
<content type="html"><![CDATA[<h1 id="参考文章"><a class="markdownIt-Anchor" href="#参考文章"></a> 参考文章</h1><ul><li><a href="https://zerotier.atlassian.net/wiki/spaces/SD/pages/224395274/Route+between+ZeroTier+and+Physical+Networks">ZeroTier · 局域网和ZeroTier网络之间的路由转发</a></li></ul><h1 id="需要解决的问题"><a class="markdownIt-Anchor" href="#需要解决的问题"></a> 需要解决的问题</h1><p>在使用非<code>OpenWRT</code>这种路由器类型的<code>Linux</code>发行版的时候,如果想要使用<code>ZeroTier</code>自带的路由转发的功能,需要进行一系列的操作来启用内部的路由转发机制。</p><p>解决这个问题原本应该十分简单,只需要在<code>sysctl</code>启用对应的<code>forward</code>设置然后设置防火墙即可。但是在<code>CentOS 8</code>上实践的过程中,遇到了<code>iptables-save</code>无法保存防火墙规则的问题。因为对<code>iptables</code>的使用也不是很频繁,这里就写了一个小脚本,通过<code>systemd</code>的方式曲线救国,并记录一下脚本的内容</p><h1 id="启用路由转发的流程"><a class="markdownIt-Anchor" href="#启用路由转发的流程"></a> 启用路由转发的流程</h1><h2 id="初期准备"><a class="markdownIt-Anchor" href="#初期准备"></a> 初期准备</h2><ol><li><p>首先将需要作为内部跳板的局域网机器加入ZeroTier网络中,这一步网上的教程比较多,不多做赘述</p></li><li><p>根据下面这个简单的脚本查询当前机器对应的默认网络<code>CIDR</code>信息</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 提取默认路由的接口名称</span><br>iface=$(route | grep default | awk <span class="hljs-string">'{print $8}'</span>)<br><br><span class="hljs-comment"># 使用接口名称获取默认网关</span><br>gateway=$(ip route show default | grep -oP <span class="hljs-string">"default via \K\S+"</span>)<br><br><span class="hljs-comment"># 使用相同接口获取子网掩码</span><br>netmask=$(ip -o -f inet addr show <span class="hljs-variable">$iface</span> | awk <span class="hljs-string">'{print $4}'</span> | sed <span class="hljs-string">'s/.*\///'</span>)<br><br><span class="hljs-comment"># 输出默认网关和子网掩码</span><br><span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">${gateway}</span>/<span class="hljs-variable">${netmask}</span>"</span><br></code></pre></td></tr></table></figure><blockquote><p>这里为了做示范,假设输出的内容为<code>192.168.1.1/24</code>,机器在<code>ZeroTier</code>子网对应的IP为<code>10.242.231.123</code></p></blockquote></li><li><p>将输出的默认网关填入<code>ZeroTier</code>控制台的<code>Manage Routes</code>处。其中<code>Destination</code>填入我们上面查询到的<code>192.168.1.1/24</code>,<code>Via</code>填入机器对应的网关IP:<code>10.242.231.123</code></p><blockquote><p>在这里设置之后,ZeroTier子网下其他设备就会将<code>192.168.1.x</code>网段的请求都转发给<code>10.242.231.123</code>。因此如果有多个物理局域网需要转发,最好确定彼此之间的网段不会冲突</p></blockquote></li></ol><h2 id="设置转发"><a class="markdownIt-Anchor" href="#设置转发"></a> 设置转发</h2><p>在初期准备完成之后,会发现此时如果想从虚拟局域网的其他设备直接通过<code>192.168.1.x</code>的网段访问内网其他设备,是无法成功通信的。这是因为默认的<code>Linux</code>发行版一般都会禁用路由转发的功能<s>来确保安全</s>。因此还需要我们给当前系统启用路由转发</p><ul><li><p>开启<code>sysctl</code>的路由转发</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo sysctl -w net.ipv4.ip_forward=1<br></code></pre></td></tr></table></figure></li></ul><p>但是在完成了上面的步骤以后,也会发现只有跳板机本身的<code>192.168.1.x</code>可以访问,局域网内其他设备不一定能直接访问。这个时候就还需要我们对防火墙的规则进行修改,放行<code>ZeroTier</code>的网络请求</p><p>对于这一步,根据官方的文档,理论上直接执行下面的步骤就能成功</p><blockquote><h3 id="configure-iptables"><a class="markdownIt-Anchor" href="#configure-iptables"></a> Configure iptables</h3><p>Assign some shell variables (personalize these)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">PHY_IFACE=eth0; ZT_IFACE=zt7nnig26<br></code></pre></td></tr></table></figure><p>Add rules to iptables</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo iptables -t nat -A POSTROUTING -o <span class="hljs-variable">$PHY_IFACE</span> -j MASQUERADE sudo iptables -A FORWARD -i <span class="hljs-variable">$PHY_IFACE</span> -o <span class="hljs-variable">$ZT_IFACE</span> -m state --state RELATED,ESTABLISHED -j ACCEPT sudo iptables -A FORWARD -i <span class="hljs-variable">$ZT_IFACE</span> -o <span class="hljs-variable">$PHY_IFACE</span> -j ACCEPT<br></code></pre></td></tr></table></figure><p>Save iptables rules for next boot</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sudo apt install iptables-persistent sudo bash -c iptables-save > /etc/iptables/rules.v4<br></code></pre></td></tr></table></figure></blockquote><p>但是在实践的过程中,发现在物理机刚启动的时候,<code>ZeroTier</code>的网卡并不会立马被添加到系统当中。因此写了一个开机自启动的轮训脚本,在监测到网卡正确加载之后,再对接口进行修改放行</p><ol><li><p>在<code>/etc/systemd/system</code>下创建服务<code>zerotier-nat.service</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs bash">[Unit]<br>Description=Setup IP forwarding and iptables rules <span class="hljs-keyword">for</span> ZeroTier<br>Wants=zerotier-one.service<br>After=zerotier-one.service network.target<br><br>[Service]<br>Type=oneshot<br>ExecStart=/usr/local/bin/enable-zerotier-nat.sh<br>RemainAfterExit=<span class="hljs-built_in">yes</span><br><br>[Install]<br>WantedBy=multi-user.target<br></code></pre></td></tr></table></figure></li><li><p>创建文件<code>/usr/local/bin/enable-zerotier-nat.sh</code>,写入下列内容</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><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></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/bash</span><br><span class="hljs-comment"># Enable IP forwarding</span><br>sysctl -w net.ipv4.ip_forward=1<br><br><span class="hljs-comment"># Get the interface with the default route (assumed to be PHY_IFACE)</span><br>PHY_IFACE=$(ip route | grep default | awk <span class="hljs-string">'{print $5}'</span>)<br><span class="hljs-built_in">echo</span> <span class="hljs-string">"Physical interface detected: <span class="hljs-variable">$PHY_IFACE</span>"</span><br><br><span class="hljs-comment"># Initialize attempt counter</span><br>attempt=0<br><br><span class="hljs-comment"># Try to find the ZeroTier interface, retry up to 10 times every 5 seconds</span><br><span class="hljs-keyword">while</span> [ <span class="hljs-variable">$attempt</span> -lt 10 ]; <span class="hljs-keyword">do</span><br> ZT_IFACE=$(ip <span class="hljs-built_in">link</span> show | grep -o <span class="hljs-string">'zt[a-zA-Z0-9]*'</span>)<br> <span class="hljs-keyword">if</span> [ ! -z <span class="hljs-string">"<span class="hljs-variable">$ZT_IFACE</span>"</span> ]; <span class="hljs-keyword">then</span><br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"ZeroTier interface detected: <span class="hljs-variable">$ZT_IFACE</span>"</span><br> <span class="hljs-built_in">break</span><br> <span class="hljs-keyword">else</span><br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"ZeroTier interface not found, retrying in 5 seconds..."</span><br> <span class="hljs-built_in">sleep</span> 5<br> ((attempt++))<br> <span class="hljs-keyword">fi</span><br><span class="hljs-keyword">done</span><br><br><span class="hljs-comment"># Check if ZT_IFACE was found</span><br><span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-variable">$ZT_IFACE</span>"</span> ]; <span class="hljs-keyword">then</span><br> <span class="hljs-built_in">echo</span> <span class="hljs-string">"Failed to find ZeroTier interface after 10 attempts."</span><br> <span class="hljs-built_in">exit</span> 1<br><span class="hljs-keyword">fi</span><br><br><span class="hljs-comment"># Set up NAT</span><br>iptables -t nat -A POSTROUTING -o <span class="hljs-variable">$PHY_IFACE</span> -j MASQUERADE<br><br><span class="hljs-comment"># Set up IP forwarding rules</span><br>iptables -A FORWARD -i <span class="hljs-variable">$PHY_IFACE</span> -o <span class="hljs-variable">$ZT_IFACE</span> -m state --state RELATED,ESTABLISHED -j ACCEPT<br>iptables -A FORWARD -i <span class="hljs-variable">$ZT_IFACE</span> -o <span class="hljs-variable">$PHY_IFACE</span> -j ACCEPT<br></code></pre></td></tr></table></figure></li><li><p>为刚刚编辑的脚本添加可执行权限,并设置脚本的开机自启动</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">chmod</span> +x /usr/local/bin/enable-zerotier-nat.sh<br>systemctl <span class="hljs-built_in">enable</span> --now zerotier-nat<br></code></pre></td></tr></table></figure></li></ol><p>经过上面的操作之后,理论上就完成了,可以在<code>ZeroTier</code>其他设备上直接访问<code>192.168.1.x</code>的家庭内网设备啦</p>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>ZeroTier</tag>
</tags>
</entry>
<entry>
<title>原理分析:UDP和TCP在NAT环境下的P2P打洞实现</title>
<link href="/p/bb3a9deb.html"/>
<url>/p/bb3a9deb.html</url>
<content type="html"><![CDATA[<h1 id="参考文章"><a class="markdownIt-Anchor" href="#参考文章"></a> 参考文章</h1><ul><li><a href="https://bford.info/pub/net/p2pnat/index.html">Bryan Ford’s Home Page (bford.info)</a></li></ul><h1 id="p2p的特点"><a class="markdownIt-Anchor" href="#p2p的特点"></a> P2P的特点</h1><p>在当前互联网的结构模式下,大部分的数据通信和交互都是以C/S结构进行通信,即一个客户端和一个中心服务器,客户端通过将数据交给服务器,再有服务器将数据进行适当的处理后与客户端进行交互。除了C/S,还有一种常见的结构,即P2P通信。在P2P网络下,主要的通信双方为“节点”,节点和节点之间的通信是直达的,不需要中心服务器对信息进行处理。</p><p>由于这篇博客本身的目的并不是非要在C/S和P2P中抉择出一个好坏,重点主要放在P2P的技术实现上,就不做优劣对比了。P2P网络本质上是一种去中心化的网络结构,每个节点都直接与其他节点交互,共享节点和节点之间的资源与服务。这种结构相对来说可以更有效的利用资源,提高传输效率和可靠性(打洞成功的情况下)。</p><p>但是既然有这么多好处,那么必然也有相对应的挑战:P2P网络最核心的本质还是需要节点和节点能够直接通信。但是在当前国内的网络环境下,大部分的家庭用户并没有一个自己的公网IP。很多时候想实现节点和节点的直接通信,我们都需要想一个办法跨过防火墙,来让两个节点能够“握手”并“通信”,而这个过程便称为“打洞”。</p><h1 id="nat的通信方式"><a class="markdownIt-Anchor" href="#nat的通信方式"></a> NAT的通信方式</h1><p><img src="https://lsky.halc.top/Gt6JKG.png" alt="NAT通信流程图" /></p><ol><li><strong>内网请求</strong>:用户设备(内网IP地址为192.168.1.10)通过端口5000发起对外部服务器的请求。</li><li><strong>地址转换</strong>:路由器接收到来自内网用户的请求后,使用NAT机制将源地址从内网IP转换为路由器的公网IP地址(203.0.113.45),同时,源端口从5000更改为1024。NAT通过这一端口映射的过程,来维护外网和内网设备的通信。</li><li><strong>请求转发</strong>:经过地址转换后,路由器将修改后的请求通过互联网转发至目标服务器(IP地址为51.68.141.240)的80端口。s</li><li><strong>服务器响应</strong>:服务器接收到请求后,对该请求进行处理,并通过相同的端口(80)向互联网发送响应数据。</li><li><strong>响应数据路由</strong>:互联网将服务器的响应数据发回路由器的公网IP地址(203.0.113.45)的1024端口。</li><li><strong>反向地址转换</strong>:路由器接收到响应数据后,再次使用NAT机制,将响应数据包的目标地址从路由器的“公网IP地址(203.0.113.45):端口(1024)”映射回用户的”内网IP地址(192.168.1.10)和端口5000“。</li><li><strong>内网传递响应</strong>:最后,路由器将响应数据发送回内网用户,完成整个通信过程。</li></ol><h1 id="udp的打洞过程"><a class="markdownIt-Anchor" href="#udp的打洞过程"></a> UDP的打洞过程</h1><h2 id="建立点对点会话连接"><a class="markdownIt-Anchor" href="#建立点对点会话连接"></a> 建立点对点会话连接</h2><p><img src="https://lsky.halc.top/KZN0EJ.png" alt="image-20240130172638076" /></p><ol><li><strong>注册与穿透服务器</strong>:两个客户端A和B分别与穿透服务器S建立UDP会话。在此过程中,服务器S记录每个客户端的两个<code>Endpoint</code>:客户端自认为用来与S通信的内网<code>Endpoint</code>对,以及服务器观察到的客户端用来进行通信的公网<code>Endpoint</code>。</li><li><strong><code>Endpoint</code>信息的记录与交换</strong>:服务器S将从客户端收到的注册消息中提取内网<code>Endpoint</code>信息,并从IP和UDP头部提取公网<code>Endpoint</code>信息。如果客户端不在NAT后面,这两个<code>Endpoint</code>应该是相同的。</li><li><strong>发起UDP打洞请求</strong>:假设客户端A想要直接与客户端B建立UDP会话。A首先不知道如何到达B,因此A请求S帮助建立与B的UDP会话。</li><li><strong>服务器响应</strong>:S回复A,包含B的公网和内网<code>Endpoint</code>信息。同时,S使用其与B的UDP会话向B发送包含A的公网和内网<code>Endpoint</code>的连接请求消息。收到这些消息后,A和B知道了对方的公网和内网<code>Endpoint</code>。</li><li><strong>双向打洞</strong>:A收到B的公网和内网<code>Endpoint</code>后,开始向这两个<code>Endpoint</code>发送UDP数据包,并锁定首先从B处获得有效响应的<code>Endpoint</code>。类似地,B在收到转发的连接请求后,开始向A的已知<code>Endpoint</code>发送UDP数据包,锁定第一个有效的<code>Endpoint</code>并以此通讯。由于A和B相互发送数据包的操作本身是异步的,因此A和B发送数据包的前后顺序并没有严格要求。</li></ol><h2 id="不同nat下打洞的过程"><a class="markdownIt-Anchor" href="#不同nat下打洞的过程"></a> 不同NAT下打洞的过程</h2><h3 id="客户端a和b都在同一个nat后"><a class="markdownIt-Anchor" href="#客户端a和b都在同一个nat后"></a> 客户端A和B都在同一个NAT后</h3><p><img src="https://lsky.halc.top/4nATYZ.png" alt="image-20240131145913368" /></p><ol><li><p><strong>建立会话</strong>:客户端A与服务器S建立UDP会话,NAT分配公网端口62000。</p></li><li><p><strong>端口分配</strong>:客户端B也与服务器S建立UDP会话,NAT为其分配公网端口62005。</p></li><li><p><strong>连接请求</strong>:客户端A请求使用打洞技术与客户端B建立通信,并通过服务器S作为介绍人。</p></li><li><p><strong>交换Endpoint信息</strong>:服务器S向客户端A发送客户端B的公网和内网Endpoint信息,并将客户端A的信息转发给客户端B。</p></li><li><p><strong>尝试直接通信</strong>:客户端A和B尝试向彼此的公网和内网Endpoint发送UDP数据包。</p></li><li><p><strong>选择通信路径</strong>:根据NAT的支持情况,客户端可能通过NAT(支持Hairpin转译)或直接(不支持Hairpin转译)进行通信。</p><blockquote><p><strong>Hairpin转译</strong>:</p><p>Hairpin是NAT中的一种转译技术,其主要实现了让NAT后的两台设备都可以通过公网的IP和端口进行直接通信,具体的效果如下</p><ol><li><strong>客户端A发送数据</strong>:假设客户端A想要发送数据到客户端B。首先,客户端A会将数据包发送到NAT设备,目标是客户端B的公网Endpoint(例如155.99.25.11:62005)。</li><li><strong>NAT设备处理</strong>:NAT设备接收到来自客户端A的数据包,并查看目标地址。这里涉及Hairpin转译,因为数据包的源地址和目标地址都是由NAT设备分配的公网地址。</li><li><strong>Hairpin转译动作</strong>:如果NAT设备支持Hairpin NAT,它会识别出虽然目标地址是公网地址,但实际上目的地是内网中的另一个客户端。NAT设备将会将数据包的目标地址从B的公网Endpoint转换为B的内网IP地址(10.1.1.3),同时可能还会更改源地址从A的公网Endpoint到A的内网IP地址(10.0.0.1)。</li><li><strong>数据包转发给客户端B</strong>:完成地址转换后,NAT设备将数据包转发到客户端B的内网地址上。此时,数据包好像是从客户端A直接发送给客户端B而不是经过互联网,即使它们实际上是通过NAT设备的公网地址进行通信的。</li><li><strong>客户端B接收数据</strong>:客户端B收到了来自客户端A的数据包,尽管这些数据包最初是发送到NAT设备的公网地址的。</li></ol><p>在P2P通信中,由于内网Endpoint比公网的Endpoint要更早到达客户端B,也就是说Hairpin转译的通信流程还没走完,客户端A通过内网Endpoint和B建立的通信就完成了。因此在实际的通信中,由于内网路由通常比经过NAT的路由更快,客户端A和B更倾向于使用内网Endpoint进行后续的常规通信。</p></blockquote></li></ol><h3 id="客户端a和b在不同nat后"><a class="markdownIt-Anchor" href="#客户端a和b在不同nat后"></a> 客户端A和B在不同NAT后</h3><p><img src="https://lsky.halc.top/GPPOFe.png" alt="不同NAT后打洞的原理" /></p><ol><li><strong>会话初始化</strong>:客户端A和B分别从它们的本地端口4321发起到服务器S的1234端口的UDP通信会话。</li><li><strong>端口映射</strong>:NAT A为客户端A分配公网端口62000,而NAT B为客户端B分配公网端口31000。</li><li><strong>注册与记录</strong>:A和B向服务器S注册它们的内网和公网Endpoint。</li><li><strong>请求协助</strong>:客户端A请求服务器S帮助与客户端B建立连接。</li><li><strong>Endpoint交换</strong>:服务器S向两个客户端交换彼此的公网和内网Endpoint信息。</li><li><strong>尝试直连</strong>:A和B尝试直接向彼此的公网和内网Endpoint发送UDP数据包。</li><li><strong>NAT行为</strong>:如果NAT A和NAT B表现良好,它们将保留公网到内网的映射,为P2P通信“打洞”。</li><li><strong>通信验证</strong>:一旦客户端验证了公网Endpoint的可用性,且因为在两个不同的NAT后,内网Endpoint不可达,它们将停止向内网Endpoint发送消息,只用公网Endpoint通信。</li></ol><h3 id="客户端a和b在多层nat后"><a class="markdownIt-Anchor" href="#客户端a和b在多层nat后"></a> 客户端A和B在多层NAT后</h3><p><img src="https://lsky.halc.top/glofU4.png" alt="多层NAT下打洞的原理" /></p><ol><li><strong>客户端发起连接</strong> - 客户端A和B分别从它们的内网地址发起到服务器S的UDP连接。</li><li><strong>NAT A和B映射</strong> - NAT A和NAT B各自为客户端A和B创建了公网到内网的地址映射。</li><li><strong>NAT C建立映射</strong> - 在ISP级别的NAT C为两个会话建立了公网到内网的地址映射。</li><li><strong>尝试建立P2P连接</strong> - 客户端A和B尝试通过UDP打洞技术建立直接的P2P连接。</li><li><strong>NAT C的Hairpin转译</strong> - 如果NAT C支持Hairpin转译,它会处理从A到B和从B到A的数据包。</li><li><strong>数据包路由</strong> - NAT C将数据包正确地路由到另一端的客户端。</li><li><strong>数据包到达目的地</strong> - 经过NAT的转译,数据包成功到达对方客户端。</li></ol><p>当NAT不支持Hairpin转发的时候就无能为力了,目前Hairpin的普及度也需要打一个问号。也存在一些特殊的NAT结构,让P2P的成功率更加没有保证。如果希望P2P打洞的成功率变高,则需要整个互联网都推动这一块的发展。</p><h2 id="打洞成功后的空闲超时机制"><a class="markdownIt-Anchor" href="#打洞成功后的空闲超时机制"></a> 打洞成功后的空闲超时机制</h2><p>即使在通过上述的几种不同的方法打洞成功,这种方法打出的隧道也并不是可以一直可靠的。大部分的NAT内部都有一个维护UDP转换信息的计时器:如果在一段时间内某个端口上不再有数据通信,那么这个隧道就会因为空闲超时被关闭掉。</p><h3 id="维持隧道连接"><a class="markdownIt-Anchor" href="#维持隧道连接"></a> 维持隧道连接</h3><p>如果希望P2P的隧道能不受NAT网关的时间限制,就需要通过发送持续的心跳包来维持这个隧道的活跃状态。</p><p>除了心跳包的方法,当然也可以在双方长时间没有数据往来的时候将当前的隧道关闭,并在下一次需要通信的时候建立连接。通过这样的方式避免不必要的流量浪费。</p><h1 id="使用tcp实现p2p打洞"><a class="markdownIt-Anchor" href="#使用tcp实现p2p打洞"></a> 使用TCP实现P2P打洞</h1><p>与UDP协议相比,使用TCP实现P2P打洞最大的问题并不在于TCP诸如三次握手等协议层的问题。相比之下,由于TCP拥有诸如<code>SYN_SENT</code>和<code>ESTABLISHED</code>这种状态描述来记录一个会话的具体生命周期,使用TCP进行P2P要比使用UDP更加健壮一些。缺点是由于TCP的打洞目前还未推广开来,因此支持TCP打洞的设备并不多。</p><h2 id="sockets与tcp端口的复用"><a class="markdownIt-Anchor" href="#sockets与tcp端口的复用"></a> Sockets与TCP端口的复用</h2><p>在操作系统的通信API当中,如果要使用<code>Socket</code>建立<code>TCP</code>的连接,可以使用<code>connect()</code>方法来发送一个请求;或使用<code>listen()</code>和<code>accept()</code>来监听一个请求。但是相比UDP,正常情况下TCP要求一个端口仅能绑定一个<code>Socket</code>通信。如果想要绑定第二个的话则会失败。</p><p>如果我们需要实现一个TCP的打洞,我们需要有一个端口,可以在监听请求的同时对外发送请求。为了实现这一点,我们需要使用操作系统中的一种特殊的<code>Socket</code>:通过在<code>TCP Socket</code>携带<code>SO_REUSEADDR</code>这个特殊的关键字,我们就可以实现复用一个TCP端口,绑定多个<code>Sockets</code>。但这么做也是有限制的:所有绑定在这个端口上的<code>Socket</code>请求,都必须要携带<code>SO_REUSEADDR</code>这个关键字。</p><blockquote><p>在类似BSD的系统中,除了针对端口的绑定,还有<code>SO_REUSEPORT</code>这个关键字,用于区分具体是复用端口还是复用地址。这个时候就需要将两个参数同时设置才能生效。</p></blockquote><h2 id="建立tcp的p2p数据流"><a class="markdownIt-Anchor" href="#建立tcp的p2p数据流"></a> 建立TCP的P2P数据流</h2><p>其实TCP打洞的过程和UDP的打洞过程本质并没有很大区别。都是通过握手服务器获取到了对方的 Endpoint 信息后,同时尝试对内网和公网的地址进行访问。</p><p><img src="https://lsky.halc.top/f9rV8M.png" alt="TCP打洞的实现" /></p><p>正如上面介绍<code>Sockets</code>之于TCP的端口复用中提到的:在TCP协议中,开发者需要维护多个<code>Socket</code>,分别来处理监听和信息的发送;而对于UDP来说,只需要维护一个<code>Socket</code>,就可以实现客户端和客户端之间的信息交互。</p><h2 id="tcp连接的握手过程"><a class="markdownIt-Anchor" href="#tcp连接的握手过程"></a> TCP连接的握手过程</h2><p>在TCP打洞技术中,客户端应用程序根据操作系统的不同,可能观察到两种不同的行为。这两种行为反映了不同TCP实现对同步包(SYN)的处理方式的差异。在这里我们假设A向B发送的SYN数据包被B的防火墙丢弃了,但是B向A的Endpoint发送的SYN数据包可以正常抵达(即至少有一方可握手成功)。</p><h3 id="基于bsd的操作系统行为"><a class="markdownIt-Anchor" href="#基于bsd的操作系统行为"></a> 基于BSD的操作系统行为:</h3><p><img src="https://lsky.halc.top/oHhxGK.png" alt="基于BSD的操作系统" /></p><p>简单来说,A的网络程序接收到了B发来的SYN信号,这个信号的Endpoint对应了A之前试图发出去的信号回应。于是,A的网络部件就把这个新信号和它原来用来尝试联系B的那个通道(socket)联系起来了。这样一来,A尝试连接B的<code>connect()</code>就成功了,而且A用来等待别人的监听socket并没有被使用到。</p><h3 id="linux和windows系统的行为"><a class="markdownIt-Anchor" href="#linux和windows系统的行为"></a> Linux和Windows系统的行为:</h3><p><img src="https://lsky.halc.top/cd4KWj.png" alt="Linux和Windows操作系统" /></p><p>在打洞过程中,A收到了B发出的SYN信号。这个信号与A尝试向B发起的连接请求相对应,因此A的TCP实现决定将这个新的连接尝试与原本用于尝试连接B的<code>socket</code>关联起来。</p><p>接着,A通过向B发送SYN-ACK响应,继续常规的TCP连接建立流程。然而,因为A先前向B尝试发起的<code>connect()</code>操作使用了和新的<code>socket</code>相同的源和目标Endpoint,所以<code>connect()</code>操作最终会失败,而用于接受B传来的SYN的新建立的<code>socket</code>下的<code>accept()</code>方法则会成功。</p><h2 id="顺序打洞"><a class="markdownIt-Anchor" href="#顺序打洞"></a> 顺序打洞</h2><p>在一些老旧的Windows系统上,当双方想要进行通信的时候,打洞这个操作不一定是并行的,他有可能是一个顺序下来的步骤。</p><ol><li>A告诉S,它想和B通信,但A这边无法正常接受数据包。</li><li>然后B尝试通过<code>connect()</code>发送一个信号给A,希望通过S的帮助到达A。但因为A还没准备好,所以B的尝试失败了。</li><li>B告诉S,它完成了尝试,并开始准备通过<code>listen()</code>接收A的信号。</li><li>S告诉A,现在轮到你尝试直接联系B了。</li></ol><p>之所以有这种应用场景的需求,是因为在一些老旧的系统上并不能够并发的打开TCP连接,或<code>socket</code>接口没有实现<code>SO_REUSEADDR</code>的关键字。这种方法相对于并发连接来说速度会更慢一些,同时这种方法也需要与S服务器能够一直存在连接。而现在主流的操作系统在建立了P2P连接之后就不需要依赖于服务器S的连接了。</p><h1 id="当前nat网络现状"><a class="markdownIt-Anchor" href="#当前nat网络现状"></a> 当前NAT网络现状</h1><blockquote><p>以下部分内容来源于 GPT-4 的总结</p></blockquote><p>为了使上文提到的打洞技术能够顺利工作,NAT必须具备一些关键的行为特性。虽然不是所有现有的NAT都符合这些要求,但许多NAT已经做到了,并且随着NAT厂商逐渐认识到对点对点(P2P)协议的需求(例如IP语音和在线游戏),它们正变得更加友好于P2P网络。</p><p>这里并不旨在提供一个关于NAT应如何表现的完全或确定性的规范。我们只是提供一些信息,介绍哪些最常见的行为能够支持或阻碍P2P打洞。IETF已经启动了BEHAVE工作组,旨在为NAT行为定义官方的“最佳当前实践”。</p><p>关键特性包括:</p><ul><li><strong>一致的端点转换</strong>:NAT需要一致地将私有网络上的TCP或UDP源端点映射到一个单一的公共端点。这种NAT被称为圆锥NAT,它可以确保来自同一私有端点的所有会话通过NAT上的同一个公共端点传输。</li><li><strong>处理未请求的TCP连接</strong>:当NAT收到一个未经请求的SYN包时,它应该静默丢弃该包,而不是主动拒绝它。这样可以避免干扰TCP打洞过程。</li><li><strong>保持负载不变</strong>:一些NAT会“盲目”扫描数据包负载中的IP地址并进行转换,这种行为虽不常见,但应用程序可以通过对IP地址进行混淆来保护自己。</li><li><strong>Hairpin转换</strong>:在一些需要Hairpin转换支持的多级NAT场景中,当前NAT对此的支持很少,但随着IPv4地址空间的耗尽,支持Hairpin转换在未来NAT实现中变得重要。</li></ul><p>简而言之,为了支持P2P通信和打洞技术,NAT需要具备一些特定的行为特性,包括一致的端点转换、正确处理未请求的连接尝试、保持数据包负载不变,以及在需要时支持Hairpin转换。随着对P2P通信需求的增加,NAT技术也在逐步适应,以更好地支持这些应用。</p><p><strong>在英文原文中还有一些有关于如何衡量和测试NAT结构的方法,由于本文主要探究的是P2P打洞的技术实现,因此在这里就不做拓展了。后续如果遇到有关的技术问题则会在这里进行进一步的更新</strong></p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>DNS</tag>
</tags>
</entry>
<entry>
<title>镜像构建:Windows Cloud Image</title>
<link href="/p/b9295ba3.html"/>
<url>/p/b9295ba3.html</url>
<content type="html"><![CDATA[<h1 id="windows-cloud-镜像构建"><a class="markdownIt-Anchor" href="#windows-cloud-镜像构建"></a> Windows Cloud 镜像构建</h1><p><code>Ubuntu</code>和<code>Debian</code>等常见<code>Linux</code>系统都有官方自带<code>cloud-init</code>的<code>cloud image</code>可供下载使用,但是<code>Windows</code>系统在微软中心只找到了<code>ISO</code>镜像的下载路径。当需要在<code>pve</code>等常见虚拟化环境中部署<code>Windows</code>服务器的时候,无论是<code>virtio</code>的驱动,还是<code>iso</code>安装漫长的等待时间都是个问题,所以需要构建<code>cloud image</code>来方便快捷的进行部署</p><h2 id="构建工具"><a class="markdownIt-Anchor" href="#构建工具"></a> 构建工具</h2><p>工具使用的是提供给<code>OpenStack</code>的构建工具:</p><ul><li><a href="https://github.com/cloudbase/windows-imaging-tools">cloudbase/windows-imaging-tools: Tools to automate the creation of a Windows image for OpenStack, supporting KVM, Hyper-V, ESXi and more. (github.com)</a></li></ul><p>构建出来的镜像可以在各种虚拟化环境中部署和运行。</p><h2 id="环境准备"><a class="markdownIt-Anchor" href="#环境准备"></a> 环境准备</h2><ul><li>一台开启了<code>Hyper-V</code>的<code>Windows</code>宿主机,用于构建镜像。</li><li>一个<code>Windows Server</code>的部署镜像,这里以<code>Windows2022</code>为例。</li><li><code>Windows</code>虚拟机的<code>virtio</code>驱动,可以在<a href="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.240-1/">这里</a>下载到最新的<code>iso</code>。</li></ul><h3 id="注意事项"><a class="markdownIt-Anchor" href="#注意事项"></a> 注意事项</h3><ol><li>镜像的构建本身不会污染宿主机环境,但是需要在宿主机中启用<code>Hyper-V</code>,以便于创建虚拟机。(启用教程网上很多,不做阐述)</li><li>启用了<code>Hyper-V</code>以后,为了尽可能构建稳定独立的镜像环境,需要在<code>Hyper-V</code>中创建一个额外的网卡,用于单独构建虚拟机。</li></ol><h4 id="hyper-v-添加网卡"><a class="markdownIt-Anchor" href="#hyper-v-添加网卡"></a> Hyper-V 添加网卡</h4><ol><li><p>通过<code>Windows</code>的搜索功能打开<code>Hyper-V</code>管理器</p><p><img src="https://lsky.halc.top/0grln8.png" alt="Hyper-V管理器" /></p></li><li><p>根据下面的流程创建一个虚拟交换机</p><p><img src="https://lsky.halc.top/gRkOVr.png" alt="创建虚拟交换机" /></p></li><li><p>配置虚拟交换机属性</p><p><img src="https://lsky.halc.top/mYKhyK.png" alt="虚拟交换机属性" /></p></li><li><p>等待应用更改结束后就可以退出<code>Hyper-V</code>管理器了</p></li></ol><h4 id="挂载虚拟光驱"><a class="markdownIt-Anchor" href="#挂载虚拟光驱"></a> 挂载虚拟光驱</h4><p>只要双击打开下载好的<code>Windows2022</code>的<code>iso</code>文件,<code>Windows</code>就会自动对虚拟光驱进行挂载了。这里假设我挂载以后的盘符为<code>D:\</code></p><h2 id="构建流程"><a class="markdownIt-Anchor" href="#构建流程"></a> 构建流程</h2><ol><li><p>首先在<code>Windows</code>宿主机上克隆构建工具的仓库</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">git clone https://github.com/cloudbase/windows<span class="hljs-literal">-imaging-tools</span>.git<br></code></pre></td></tr></table></figure></li><li><p>以管理员权限执行<code>Powershell</code>,并进入刚刚<code>clone</code>的仓库目录下</p></li><li><p>为当前的<code>Powershell</code>临时加载镜像构建需要的模块</p><figure class="highlight powershell"><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><code class="hljs powershell"><span class="hljs-built_in">Import-Module</span> .\WinImageBuilder.psm1<br><span class="hljs-built_in">Import-Module</span> .\Config.psm1<br><span class="hljs-built_in">Import-Module</span> .\UnattendResources\ini.psm1<br></code></pre></td></tr></table></figure></li><li><p>指定配置文件的目录,并初始化对应的文件内容</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs powershell"><span class="hljs-variable">$ConfigFilePath</span> = <span class="hljs-string">".\config.ini"</span><br><span class="hljs-built_in">New-WindowsImageConfig</span> <span class="hljs-literal">-ConfigFilePath</span> <span class="hljs-variable">$ConfigFilePath</span><br></code></pre></td></tr></table></figure></li><li><p>(可跳过)快速初始化配置文件</p><figure class="highlight powershell"><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><code class="hljs powershell"><span class="hljs-built_in">Set-IniFileValue</span> <span class="hljs-literal">-Path</span> (<span class="hljs-built_in">Resolve-Path</span> <span class="hljs-variable">$ConfigFilePath</span>) <span class="hljs-literal">-Section</span> <span class="hljs-string">"DEFAULT"</span> `<br> <span class="hljs-literal">-Key</span> <span class="hljs-string">"wim_file_path"</span> `<br> <span class="hljs-literal">-Value</span> <span class="hljs-string">"D:\Sources\install.wim"</span> <span class="hljs-comment"># 这里的D:\是指虚拟光驱挂载到了D:\</span><br></code></pre></td></tr></table></figure><blockquote><p>因为有一些具体的参数还需要调整,因此这一步可以在下一步手动修改<code>config</code>的时候进行</p></blockquote></li><li><p>手动完善刚刚通过命令行创建的<code>config.ini</code>文件,以下是一些注意事项和自己没搞明白的地方</p><ul><li><code>image_name</code>这个配置项需要对<code>install.wim</code>执行<code>Get-WimFileImagesInfo</code>来获取一个列表,然后选出你需要的那个镜像名字。比如对带有桌面环境的<code>Windows Server 2022</code>标准版而言,就是<code>Windows Server 2022 SERVERSTANDARD</code></li><li><code>[vm]</code>的配置参数是指构建用的虚拟机的参数。在构建完成后这个虚拟机会被销毁,一般不用在意。在构建失败的时候可以通过管理员密码进入虚拟机查看信息</li><li><code>[vm]</code>的<code>external_switch</code>中需要填入的就是我们刚刚创建好的<code>Hyper-V</code>虚拟网卡名字,比如按照上图中我们选用的是<code>external</code>,这里直接填入即可。</li><li><code>[drivers]</code>的部分需要选择你下载的<code>virtio</code>镜像地址</li><li><code>time_zone</code>这块不太清楚格式应该怎么填,无论是<code>Asia/Shanghai</code>还是<code>China Standard Time</code>,在自己构建的时候都失败了。如果出现了同样问题的小伙伴建议就默认留空试试</li></ul><p>其他具体是用<code>qcow2</code>还是<code>vhdx</code>就按需填写即可</p></li><li><p>执行构建命令,等待镜像构建成功即可</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell"><span class="hljs-built_in">New-WindowsOnlineImage</span> <span class="hljs-literal">-ConfigFilePath</span> <span class="hljs-variable">$ConfigFilePath</span><br></code></pre></td></tr></table></figure><blockquote><p>镜像构建的过程中,会有一段时间<code>Powershell</code>一直输出查询日志,这个时候可以去Hyper-V管理器那边查看创建好的虚拟机中的构建状态,如果出现了报错的话直接将虚拟机删除,重新调整配置文件再构建即可。</p><p>理论上来说整个构建流程都可以无人值守完成,构建成功了一次以后配置文件和路径在确定的情况下可以保留用于下一次的构建</p></blockquote></li><li><p>构建成功后,生成的对应镜像会存放于<code>config.ini</code>中的<code>image_path</code>配置目录下。</p></li></ol><h2 id="补充说明"><a class="markdownIt-Anchor" href="#补充说明"></a> 补充说明</h2><ul><li><p>整个构建流程对<code>Windows</code>宿主机一般而言不会有环境污染的问题。在构建完成以后,只需要删掉创建的<code>Hyper-V</code>虚拟交换机就可以恢复初始环境。如果在构建用虚拟机构建过程中出现问题,只需要删掉对应的虚拟机和虚拟机对应的磁盘文件即可。</p></li><li><p>如果是在加载驱动的过程中手动中止了构建,也只需要将挂载的虚拟目录弹出或删除,然后从头开始整个流程即可。</p></li><li><p>如果构建成功的话,虚拟机会自动关机并重启,这段时间可能会存在一段时间黑屏情况,不用太在意,等待一段时间以后<code>Powershell</code>会接收到虚拟机的配置信息,从而开始镜像的打包流程。</p></li></ul>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>Windows</tag>
<tag>Cloud</tag>
</tags>
</entry>
<entry>
<title>性能测试:跨墙的内网穿透工具选择</title>
<link href="/p/59e029c8.html"/>
<url>/p/59e029c8.html</url>
<content type="html"><![CDATA[<h1 id="性能测试跨墙的内网穿透工具选择"><a class="markdownIt-Anchor" href="#性能测试跨墙的内网穿透工具选择"></a> 性能测试:跨墙的内网穿透工具选择</h1><p>由于需要将家里<code>HomeLab</code>的服务内网穿透到外网服务器上,如果使用<code>Zerotier</code>一类的服务进行穿透的话,在经过<code>GFW</code>以后速度会暴跌。因此需要使用带加密的服务来进行建立连接。</p><p>在大约一年前的时候使用了<code>Frp</code>作为跨墙的内网穿透工具,但是因为不知名的原因,当时<code>Frps</code>和<code>Frpc</code>的连接总是容易断开,当时也没有做具体的原因分析。这段时间在新购买了海外VPS之后决定花一些时间对不同的内网穿透工具在跨越<code>GFW</code>的情况下的性能和丢包做一个对比</p><h2 id="省流不看"><a class="markdownIt-Anchor" href="#省流不看"></a> 省流不看</h2><p>综合性能、带宽和丢包率来看,最新版本的<code>Frp</code>已经是不错的选择。不过目前还不确定在特殊时期<code>Frp</code>能否经历防火墙的考验,而<code>V2Ray</code>虽然在<code>Benchmark</code>的结果上不如人意,但是在跨墙可用性来说是身经百战的。<code>Rathole</code>在个人当前的应用场景下则没有太大的优势。</p><h2 id="测试流程"><a class="markdownIt-Anchor" href="#测试流程"></a> 测试流程</h2><h3 id="机器"><a class="markdownIt-Anchor" href="#机器"></a> 机器</h3><ul><li>内网虚拟机一台,配置为<code>2C4G</code>,后文简称<code>client</code>。该机器位于家庭宽带环境中,没有公网IP。</li><li>外网机器一台,配置为<code>1C1G</code>,后文简称<code>server</code>。使用的是洛杉矶的机器,国内线路有做优化。</li></ul><h3 id="测试工具"><a class="markdownIt-Anchor" href="#测试工具"></a> 测试工具</h3><ul><li>V2RAY:使用<code>ws+tls</code>协议,其中<code>tls</code>部分由<code>nginx</code>进行处理。版本为:5.4.1</li><li>FRP:服务端除了<code>auth.token</code>不添加任何多余的参数,性能方面均以默认配置文件为主。版本为:0.53.2</li><li>Rathole:同样以默认配置文件为主,最新的源码<code>65b27f076c</code>编译而来的二进制文件。版本为:0.5.0</li></ul><p>其中<code>V2RAY</code>与<code>FRP</code>均使用<code>Docker</code>的方式进行部署,<code>Rathole</code>通过<code>screen</code>直接运行在后台。</p><blockquote><p>该测试环境存在诸多不合理的地方,该测试主要为自己的特定使用场景做参考。如果有问题还请斧正</p></blockquote><h3 id="测试流程-2"><a class="markdownIt-Anchor" href="#测试流程-2"></a> 测试流程</h3><ol><li><p>通过三套不同的工具,分别将<code>client</code>上的<code>5201</code>端口转发到<code>server</code>的<code>5201(v2ray)</code>,<code>15201(frp)</code>和<code>25201(rathole)</code>上。</p></li><li><p>在<code>server</code>端输入以下指令,以<code>Json</code>文件获取<code>120s</code>内<code>tcp</code><strong>连接的带宽和稳定性</strong></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">iperf3 -J --logfile ./xxx_result.log -c localhost -p xxx -t 120s<br></code></pre></td></tr></table></figure></li><li><p>将测试结果统计后以可视化的形式进行对比</p></li></ol><h2 id="结果分析"><a class="markdownIt-Anchor" href="#结果分析"></a> 结果分析</h2><h3 id="配置难度"><a class="markdownIt-Anchor" href="#配置难度"></a> 配置难度</h3><ul><li>V2Ray:部署复杂度较高,配置文件可读性较差。对于<code>ws+tls</code>来说,不但要部署<code>v2ray</code>本身,在服务端还需要额外的域名和<code>Nginx</code>进行转发。同时如果我有多个<code>client</code>的时候,会出现不确定到底是代理到哪个机器的问题,在不进行额外配置的情况下容易出现<code>502</code>报错。</li><li>FRP;部署难度极低,配置文件可读性极好,上手难度极低。</li><li>Rathole:部署难度中等,每次添加端口需要同时修改服务端和客户端的配置文件,后续维护是最麻烦的。</li></ul><h3 id="性能对比"><a class="markdownIt-Anchor" href="#性能对比"></a> 性能对比</h3><p>在通过上述测试流程后,对日志进行分析统计,可以得到以下统计图表:</p><p><img src="https://lsky.halc.top/bnWSC3.png" alt="性能测试" /></p><ul><li>带宽:<code>Frp</code>和<code>Rathole</code>没有明显差距,<code>V2Ray</code>的带宽相对来说较低,但是没有拉开明显差距</li><li>丢包率:<code>Frp</code>相比<code>Rathole</code>略有优势,在晚高峰的时候依旧能保证丢包率控制在30以下。<code>V2Ray</code>相比之下则更容易丢包</li><li>由于网站只有小范围用户使用,因此对拥塞控制并没有很高的需求。不过从这份<code>Benchmark</code>中也可以看出,在高并发场景下,<code>Rathole</code>可能会更有优势</li><li>资源占用:<code>V2Ray</code>由于加密解密的严谨性,资源消耗相比<code>Rathole</code>与<code>Frp</code>都要多。在资源占用这一点上,<code>Frp</code>几乎是毫无悬念的领先</li></ul><h3 id="综合考虑"><a class="markdownIt-Anchor" href="#综合考虑"></a> 综合考虑</h3><p>综上,从配置难度以及性能对比的角度来看,暂时使用<code>Frp</code>会是一个不错的选择。如果后续出现了性能的不稳定性也会在这里进行补充说明。</p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>Frp</tag>
<tag>Rathole</tag>
<tag>V2Ray</tag>
<tag>内网穿透</tag>
</tags>
</entry>
<entry>
<title>获取Cloudflare Tunnel下用户真实IP</title>
<link href="/p/f09b73d7.html"/>
<url>/p/f09b73d7.html</url>
<content type="html"><![CDATA[<h1 id="cloudflare-tunnel-获取用户真实-ip"><a class="markdownIt-Anchor" href="#cloudflare-tunnel-获取用户真实-ip"></a> Cloudflare Tunnel 获取用户真实 IP</h1><h2 id="参考资料"><a class="markdownIt-Anchor" href="#参考资料"></a> 参考资料</h2><ul><li><a href="https://developers.cloudflare.com/support/troubleshooting/restoring-visitor-ips/restoring-original-visitor-ips/">Restoring original visitor IPs · Cloudflare Support docs</a></li></ul><h2 id="问题分析"><a class="markdownIt-Anchor" href="#问题分析"></a> 问题分析</h2><ol><li>部署在内网中,在<code>80</code>端口部署了<code>PHP</code>的服务器一台。</li><li>使用了<code>Cloudflare Tunnel</code>对内网<code>http://127.0.0.1</code>进行了转发,并提供<code>https</code>支持</li></ol><p><code>SSPanel</code>需要拥有<code>https</code>的情况下才可以正常使用,但是使用<code>Cloudflare Tunnel</code>默认的设置的情况下,所有的用户登入请求都会被记录为<code>Tunnel</code>的转发地址(在这种情况下即<code>127.0.0.1</code>)。</p><p>在这种情况下,当用户通过<code>Tunnel</code>访问我的网站时,<code>Cloudflare</code>会通过<code>CF-Connecting-IP</code>这个<code>HTTP</code>请求头传递原始访问者的IP地址。因此也就可以通过修改<code>Nginx</code>配置的方法来获取到对应用户的真实IP地址了。</p><h2 id="解决方案"><a class="markdownIt-Anchor" href="#解决方案"></a> 解决方案</h2><p>先在这里贴上修改了的配置文件和注释</p><figure class="highlight nginx"><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></pre></td><td class="code"><pre><code class="hljs nginx"> <span class="hljs-section">server</span> {<br> <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;<br> <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">80</span>;<br><br> <span class="hljs-attribute">root</span> /path/to/site/public;<br> <span class="hljs-attribute">index</span> index.php;<br> <span class="hljs-attribute">server_name</span> your.domain.com;<br><br>+ <span class="hljs-comment"># 设置 Cloudflare 的真实 IP 地址</span><br>+ <span class="hljs-attribute">set_real_ip_from</span> <span class="hljs-number">127.0.0.1</span>/<span class="hljs-number">32</span>; <span class="hljs-comment"># Cloudflare Tunnel 的 IP 地址</span><br>+ <span class="hljs-attribute">real_ip_header</span> CF-Connecting-IP;<br><br> <span class="hljs-section">location</span> / {<br> <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> /index.php<span class="hljs-variable">$is_args</span><span class="hljs-variable">$args</span>;<br> }<br><br> <span class="hljs-section">location</span> <span class="hljs-regexp">~ \.php$</span> {<br> <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$fastcgi_script_name</span> =<span class="hljs-number">404</span>;<br> <span class="hljs-attribute">include</span> fastcgi_params;<br> <span class="hljs-attribute">fastcgi_index</span> index.php;<br> <span class="hljs-attribute">fastcgi_buffers</span> <span class="hljs-number">8</span> <span class="hljs-number">16k</span>;<br> <span class="hljs-attribute">fastcgi_buffer_size</span> <span class="hljs-number">32k</span>;<br> <span class="hljs-attribute">fastcgi_pass</span> unix:/run/php/php-fpm.sock;<br> <span class="hljs-attribute">fastcgi_param</span> DOCUMENT_ROOT <span class="hljs-variable">$realpath_root</span>;<br> <span class="hljs-attribute">fastcgi_param</span> SCRIPT_FILENAME <span class="hljs-variable">$realpath_root</span><span class="hljs-variable">$fastcgi_script_name</span>;<br>+ <span class="hljs-attribute">fastcgi_param</span> REMOTE_ADDR <span class="hljs-variable">$remote_addr</span>; <span class="hljs-comment"># 传递真实 IP 地址给 PHP</span><br> }<br> }<br></code></pre></td></tr></table></figure><p>对于上面修改的不同配置项的作用如下</p><ol><li><p><code>set_real_ip_from 127.0.0.1/32;</code></p><ul><li><code>set_real_ip_from</code>指令用于指定哪些IP地址可以设置真实IP地址。</li><li>在这种情况下,<code>127.0.0.1/32</code>表示本地地址。这是因为Cloudflare Tunnel将流量转发到您的本地服务器,通常表现为来自127.0.0.1(即本地主机)的请求。</li><li>这条指令告诉Nginx,如果请求来自本地主机(<code>127.0.0.1/32</code>),则应该考虑使用另一个HTTP头中的IP地址作为访问者的真实IP地址。</li></ul></li><li><p><code>real_ip_header CF-Connecting-IP;</code></p><ul><li><code>real_ip_header</code>指令用于指定包含真实客户端IP地址的HTTP头。</li><li><code>CF-Connecting-IP</code>是由Cloudflare设置的一个特殊的HTTP头,它包含了发起请求的原始访问者的IP地址。</li><li>通过这个配置,Nginx将使用<code>CF-Connecting-IP</code>头中的值来重写访问者的IP地址,这样PHP应用程序就可以获取到访问者的真实IP,而不是Cloudflare Tunnel的本地地址。</li></ul></li><li><p>在PHP FastCGI 配置块中添加的指令:</p><ul><li><code>fastcgi_param REMOTE_ADDR $remote_addr;</code><ul><li>这一行将Nginx内部变量<code>$remote_addr</code>的值传递给FastCGI进程。由于我们已经使用<code>set_real_ip_from</code>和<code>real_ip_header</code>配置了Nginx,<code>$remote_addr</code>将包含经过Cloudflare处理的真实客户端IP。</li><li>通过将此参数传递给FastCGI(在这种情况下是PHP-FPM),在PHP应用程序中的<code>$_SERVER['REMOTE_ADDR']</code>将包含正确的客户端IP地址。</li></ul></li></ul></li></ol>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>cloudflare</tag>
</tags>
</entry>
<entry>
<title>虚拟化:初识IOMMU(TODO)</title>
<link href="/p/4416e368.html"/>
<url>/p/4416e368.html</url>
<content type="html"><![CDATA[<h1 id="初识iommu"><a class="markdownIt-Anchor" href="#初识iommu"></a> 初识IOMMU</h1><p>最近在尝试给朋友的小主机安装<code>ZStack</code>作为虚拟化管理平台的时候,遇到了一个需求:通过HDMI直接将Windows虚拟机的画面输出到外界显示器。需要解决这个问题自然而然的就需要使用直通的方法将显卡直通给虚拟机。不过之前直通都是直接找别人的博客一步一步傻瓜式执行下去,对于每个指令发生了什么,以及<code>iommu</code>是如何工作的都不清楚。刚好趁着这个机会了解并记录下自己的学习历程</p><h2 id="问题来源"><a class="markdownIt-Anchor" href="#问题来源"></a> 问题来源</h2><p>我自己现在有一台基于<code>Proxmox VE</code>的<code>All in one</code>小主机了,这里就叫做主机A,而我朋友的主机则称为主机B。在安装<code>ZStack</code>之前,我原以为直通的过程依旧可以无脑用脚本来实现,但是实际执行过程中却发现在<code>PVE</code>中应该成功的<code>ACS</code>的改动在<code>ZStack</code>中却并没有成功。这便引起我了从<code>ACS</code>到<code>IOMMU</code>作用的好奇。</p><h3 id="启用iommu和acs"><a class="markdownIt-Anchor" href="#启用iommu和acs"></a> 启用IOMMU和ACS</h3><p>首先,在主机A和主机B的BIOS上都启用IOMMU的功能,可以发现原本的<code>iommu</code>分组都十分混乱,大部分设备杂糅在一起。为了解决这个问题,便有了叫做<code>ACS</code>的技术。</p><blockquote><p><strong>ACS的主要功能</strong></p><ol><li><strong>设备隔离</strong>:ACS允许对PCIe设备进行更细粒度的控制,增强了设备间的隔离。这在虚拟化环境中尤为重要,因为它可以帮助确保虚拟机之间的安全隔离,防止一个虚拟机访问另一个虚拟机的PCIe设备。</li><li><strong>控制I/O访问</strong>:ACS可以控制PCIe设备的I/O访问,例如控制哪些设备可以发起对其他设备或内存的直接内存访问(DMA)。</li><li><strong>提高安全性</strong>:通过对设备间访问的更严格控制,ACS有助于提高系统的整体安全性,尤其是在多租户或需要高安全性的环境中。</li></ol></blockquote><p>通过启用<code>iommu</code>的同时启用<code>acs</code>,就可以将系统中<code>iommu</code>的<code>group</code>分成更细的设备单位,具体修改的操作实现参数可能不尽相同,但是基本上都是先对<code>/etc/default/grub</code>中的<code>GRUB_CMDLINE_LINUX</code>进行修改,添加<code>amd_iommu=on</code>和<code>pcie_acs_override=downstream,multifunction</code>即可</p><h3 id="查看分组情况"><a class="markdownIt-Anchor" href="#查看分组情况"></a> 查看分组情况</h3><p>对于分组情况的查看,在<a href="https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF">PCI passthrough via OVMF - ArchWiki (archlinux.org)</a>可以找到一个脚本来列出当前的<code>IOMMU Groups</code>的情况</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">#</span><span class="language-bash">!/bin/bash</span><br>shopt -s nullglob<br>for g in $(find /sys/kernel/iommu_groups/* -maxdepth 0 -type d | sort -V); do<br> echo "IOMMU Group ${g##*/}:"<br> for d in $g/devices/*; do<br> echo -e "\t$(lspci -nns ${d##*/})"<br> done;<br>done;<br></code></pre></td></tr></table></figure><p>当我在自己的<code>PVE</code>主机上执行这个脚本之后,可以发现显卡是单独在一个<code>Groups</code>中的</p><p><img src="https://lsky.halc.top/RACX6h.png" alt="IOMMU分组" /></p><p>但是在<code>ZStack</code>平台上执行以后可以发现核显并不是单独在一个<code>Groups</code>里面的</p><blockquote><p>当时的log没有保存,这里假象一个其他的例子代替,是一个GTX 970显卡的例子,一般来说独显是会有单独的Groups的</p></blockquote><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></pre></td><td class="code"><pre><code class="hljs shell">......<br>IOMMU Group 13:<br>06:00.0 VGA compatible controller: NVIDIA Corporation GM204 [GeForce GTX 970] [10de:13c2] (rev a1)<br>06:00.1 Audio device: NVIDIA Corporation GM204 High Definition Audio Controller [10de:0fbb] (rev a1)<br>00:1d.0 USB controller: Intel Corporation 7 Series/C210 Series Chipset Family USB Enhanced Host Controller #1 <br>00:1a.0 USB controller: Intel Corporation 7 Series/C210 Series Chipset Family USB Enhanced Host Controller #2 <br>......<br></code></pre></td></tr></table></figure><p>具体问题的产生原因还不清楚(找到问题以后会来更新博客),不过就<code>IOMMU</code>的分组情况大概是这样。</p><h2 id="iommu"><a class="markdownIt-Anchor" href="#iommu"></a> IOMMU</h2><p>对于什么是<code>iommu</code>,找到这样一个简单扼要的概括</p><blockquote><p>大家知道,I/O设备可以直接存取内存,称为DMA(Direct Memory Access);DMA要存取的内存地址称为DMA地址(也可称为BUS address)。在DMA技术刚出现的时候,DMA地址都是物理内存地址,简单直接,但缺点是不灵活,比如要求物理内存必须是连续的一整块而且不能是高位地址等等,也不能充分满足虚拟机的需要。后来dmar就出现了。 dmar意为DMA remapping,是Intel为支持虚拟机而设计的I/O虚拟化技术,I/O设备访问的DMA地址不再是物理内存地址,而要通过DMA remapping硬件进行转译,DMA remapping硬件会把DMA地址翻译成物理内存地址,并检查访问权限等等。负责DMA remapping操作的硬件称为IOMMU。做个类比:大家都知道MMU是支持内存地址虚拟化的硬件,MMU是为CPU服务的;而IOMMU是为I/O设备服务的,是将DMA地址进行虚拟化的硬件。</p></blockquote><p>而在这其中,<code>IOMMU</code>分组则是实现设备直通的关键,每个分组都包含了可以共享一同一个虚拟内存映射的设备集合。如果一个设备独占一个<code>IOMMU</code>分组,直通它是很简单的。但是如果多个设备共享同一个分组,比如显卡和<code>USB</code>接口,那么就无法只直通其中一个设备。</p><h2 id="todo"><a class="markdownIt-Anchor" href="#todo"></a> TODO</h2><p>后续就是找时间尝试研究为什么<code>ACS</code>在<code>ZStack</code>中没有正常被启用,并将结论补充到这篇博客当中。</p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>iommu</tag>
</tags>
</entry>
<entry>
<title>知识复盘:操作系统的作用</title>
<link href="/p/e429c37a.html"/>
<url>/p/e429c37a.html</url>
<content type="html"><![CDATA[<h1 id="操作系统的作用"><a class="markdownIt-Anchor" href="#操作系统的作用"></a> 操作系统的作用</h1><blockquote><p>该部分博客为自己在学习《程序员的自我修养:链接、装载与库》的时候对于过去零碎知识点的一个整理和复盘,并非照搬原文,其中会加入一些自己的联想与理解,如有错误还请指出。</p></blockquote><p>操作系统在计算机中主要有两个功能:</p><ul><li><p>对硬件资源进行管理,让硬件尽可能高效的解决问题或执行操作</p></li><li><p>提供抽象的接口,以便于程序对计算机的硬件资源进行调用</p></li></ul><h2 id="cpu的调度"><a class="markdownIt-Anchor" href="#cpu的调度"></a> CPU的调度</h2><p>在计算机的使用过程中,需要消耗时间的任务部分大致可以分为两种情况:</p><ul><li>消耗CPU算力的计算密集型操作</li><li>需要等待设备响应处理的I/O密集型操作</li></ul><p>我们知道在计算机中有南桥和北桥两个概念,北桥连同了CPU等高速芯片,而南桥则负责了磁盘、鼠标、键盘等低速设备。因此我们可以抽象出一个结论:<strong>计算密集型操作</strong> 和 <strong>I/O密集型操作</strong> 是由不同的设备分别处理的。接下来对于操作系统调度分析则会都以这一前提条件进行分析。</p><h3 id="第一阶段多道程序-multiprogramming"><a class="markdownIt-Anchor" href="#第一阶段多道程序-multiprogramming"></a> 第一阶段:多道程序 Multiprogramming</h3><p>假设我们现在需要完成一个很复杂的数学问题,且假设完成这个数学问题分为两个步骤:</p><ol><li>在草稿纸上"随意"的打草稿并推演计算过程,最终计算出答案(计算密集型操作)</li><li>将整个推演过程有条理并工整的誊抄在答卷上,便于他人阅读自己的答案(I/O密集型操作)</li></ol><p>那么如果我们有很多个这样的题目需要完成。那么最高效的方法自然是分配两个同学A和B。假设同学A的计算能力很强,同学B的书写则十分端正,同时可以看懂同学A的草稿,那么我们便可以让同学A只需要负责计算和打草稿,只要做完了第一题就直接开始写第二题,而同学B则在同学A开始计算第二题的过程中开始誊抄第一题的答案。这样便可以让同学A和同学B的时间都统筹利用起来,以此提升效率。</p><p>在计算机中也是同样的道理。在计算机刚刚发展的时候,每次执行一个任务都需要先让CPU计算完后,CPU还要等待诸如打印机等设备输出了结果以后,再进行下一个问题的计算。</p><p>为了解决这个问题,人们便想到使用一个监控程序来监管CPU的运算,当监控程序发现在CPU进行完毕某一次运算以后,如果后续还有其他问题需要使用CPU进行计算的话,则让CPU直接进行下一个问题的计算,而不是等待第一个问题的I/O操作进行完毕以后再进行第二个问题的计算。这就是多道程序的雏形</p><h3 id="第二阶段分时系统-time-sharing-system"><a class="markdownIt-Anchor" href="#第二阶段分时系统-time-sharing-system"></a> 第二阶段:分时系统 Time-Sharing System</h3><p>但是随着计算机功能的逐步发展,多道程序则体现出了一个弊端:那就是任务的执行需要所有人都依次排队,前面的人如果没有解决他的问题就轮不到下一个人。</p><p>假设在银行中有一个人的业务处理需要花费特别特别长的时间,从而导致后面所有人直到银行下班都没完成自己的业务,这无疑是非常令人恼火的一件事情。但是如果这个人将自己的一个任务拆分成多个不同的部分,每完成一个部分就让后面的人先处理下,这样相对而言就能顾及他人的感受,有利于提高处理问题数量的效率。</p><p>因此,我们就了分时系统的概念,在分时系统中,程序可以通过在编写的时候主动调用某个“系统调用”来实现通知操作系统我现在这部分的工作已经完成了一部分,如果后面有其他任务需要执行的话可以先执行其他的任务,再来执行我的任务。从而在一定程度上解决了阻塞问题。</p><h3 id="第三阶段-多任务系统-multi-tasking"><a class="markdownIt-Anchor" href="#第三阶段-多任务系统-multi-tasking"></a> 第三阶段: 多任务系统 Multi-tasking</h3><p>但是分时系统在计算机的衍变过程中也展现出了自己的弊端。</p><p>依据以下两个<strong>分时系统</strong>的特点:</p><ul><li>是否让出CPU是由程序自身决定的。程序需要主动调用特定的系统调用来通知操作系统它愿意让出CPU。</li><li>这种机制的问题是,如果一个程序不主动让出CPU(例如,由于编程错误或恶意行为),那么操作系统不能强制地从该程序中夺回CPU控制权。因此,其他程序可能会被迫等待,导致整个系统的响应性下降。</li></ul><p>假设我们遇到一个程序员在程序中忘记调用分时的“系统调用”,还写了一个死循环的错误代码。那么整个操作系统都将会因为这个问题而出现宕机。</p><p>由此我们就需要一个更高端的操作系统来解决我们的问题,即现代操作系统的解决方案:多任务系统。</p><p><strong>多任务系统</strong>的基础是建立在此时操作系统对所有硬件资源进行了直接的接管。而所有的应用程序都以进程(Process)的方式运行在操作系统这个大Boss之下。所有进程的调度都需要受到操作系统的管理,并且每个进程和进程之间就像是一个小房间,他们的地址空间也都是相互隔离且独立的。</p><p>在这种情况下,CPU就变成了操作系统大Boss来进行管理的一个资源,而不是和之前分时系统一样由应用自己直接对CPU进行管理了。这样的好处是可以让所有程序都听操作系统这个领导的话,而不是和之前一样我想一直占用CPU就一直占用,如果我不调用接口主动释放CPU你们谁都别想用上CPU。</p><p>对于操作系统来说,每个进程就是一个任务,每个任务则又有自己的任务优先级。对于优先级高的任务,操作系统会先进行;对优先级低的任务则后执行。如果一个进程的运行时间超过了某个限制,则会将该程序暂停以分配给其他同时间内也许更需要CPU资源的线程任务。</p><p>在此基础上还会牵扯到一些诸如多级反馈队列、上下文切换开销等问题。这里就不做过多的展开。</p><blockquote><p>这里放一个之前写OSTEP课后实验相关的博客链接,便于自己查阅</p><ul><li><a href="https://halc.top/p/b7974b6">操作系统:通过多级反馈的调度策略 - Halcyon Zone</a></li><li><a href="https://halc.top/p/4b65fa48">操作系统:程序上下文切换的开销 - Halcyon Zone</a></li></ul></blockquote><h2 id="设备驱动"><a class="markdownIt-Anchor" href="#设备驱动"></a> 设备驱动</h2><blockquote><p>有关于设备驱动的内容直接概述过于枯燥无味,因此下面这段解释为使用 GPT-4 生成的一个概述,觉得生动有趣就搬上来了</p></blockquote><p>想象一下,你正在玩一个超级复杂的电子游戏,但你只需要按下一个按钮,就能完成一个复杂的动作,比如打怪兽或跳跃。这个按钮就像是操作系统,而那些复杂的动作就是硬件的操作。你不需要知道每一个细节,只需要按下按钮,游戏就会为你完成所有的事情。</p><p>操作系统就是这样的“神奇按钮”。它位于硬件之上,为上层的应用程序提供了一个统一的方式来访问硬件。想象一下,如果每次你想在屏幕上画一条线,都需要知道你的电脑使用的是什么显卡、屏幕的大小和分辨率,然后写一大堆复杂的代码。这听起来很麻烦,对吧?但是,有了操作系统,你只需要调用一个简单的函数,比如<code>LineTo()</code>,然后操作系统会为你处理所有的细节。</p><p>在操作系统的早期,程序员确实需要直接与硬件交互,这是一件非常繁琐和复杂的事情。但随着时间的发展,操作系统逐渐成熟,它开始为程序员提供了一系列的“抽象”概念,使得程序员可以更加轻松地开发应用程序,而不需要关心硬件的细节。比如,在UNIX系统中,访问硬件设备就像访问普通文件一样简单;在Windows系统中,图形和声音设备被抽象成了特定的对象。</p><p>但是,谁来处理这些复杂的硬件操作呢?答案是:<strong>硬件驱动程序</strong>。它们是<strong>操作系统的一部分</strong>,专门负责与特定的硬件设备交互。这些驱动程序通常由硬件制造商开发,而操作系统提供了一系列的接口和框架,使得这些驱动程序可以在操作系统上运行。</p><p>最后,让我们以读取文件为例。当你想读取一个文件时,你不需要知道这个文件在硬盘上的具体位置。你只需要告诉操作系统你想读取的文件名,然后操作系统会找到这个文件在硬盘上的位置,读取它,并将数据返回给你。这一切都是由文件系统和硬盘驱动程序共同完成的。</p><p>总之,操作系统就像是一个超级英雄,它为我们处理了所有复杂的硬件操作,使得我们可以更加轻松地开发和使用计算机程序。</p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>DNS问题排查思路</title>
<link href="/p/23c3db21.html"/>
<url>/p/23c3db21.html</url>
<content type="html"><![CDATA[<h1 id="参考文章"><a class="markdownIt-Anchor" href="#参考文章"></a> 参考文章</h1><p>这篇博客主要是在推特中无意翻到了这篇博客,尝试以翻译的形式做一套笔记,分享的同时加强自己的记忆。</p><ul><li><a href="https://jvns.ca/blog/2023/07/28/why-is-dns-still-hard-to-learn/">Why is DNS still hard to learn? (jvns.ca)</a></li></ul><h1 id="系统背后做的事情"><a class="markdownIt-Anchor" href="#系统背后做的事情"></a> 系统背后做的事情</h1><p>当我们发起一个DNS请求的时候,基本上发生的就是下面两件事</p><ol><li>电脑向一个被标记为<code>resolver</code>的服务器发送一个DNS请求。</li><li><code>resolver</code>服务器首先会检查缓存,并且在必要的时候再向<code>authoritative nameservers</code>发送查询请求。</li></ol><p>但是在这两件事情背后,我们有几个问题需要思考</p><ul><li><p>解析服务器(即上面提到的<code>resolver</code>)的缓存中存放了一些什么东西?</p></li><li><p>在计算机中,在发起一个<code>DNS</code>请求的时候调用的是哪一部分的库?</p><blockquote><p>举个例子,一个请求有可能是由<code>libc</code>中提供的<code>getaddrinfo</code>发起的,这部分代码或是来自<code>glibc</code>,或是<code>musl</code>,又或者是<code>apple</code>提供的库文件;这个请求也有可能是在浏览器中发起,由浏览器进行处理;当然也有可能是某些特定的自定义实现。</p><p>在不同的阶段和方法进行<code>DNS</code>请求做的事情都会略有不同,他们或多或少会有不一样的配置、缓存以及功能。举个例子来说,直到今年(2023)<code>musl</code>的<code>DNS</code>才开始支持<code>TCP</code>询问</p></blockquote></li><li><p>解析器和权威域名服务器(即上文中提到的<code>authoritative nameservers</code>)之间是如何进行通话的?</p><blockquote><p>在这里我们如果能知道在<code>DNS</code>请求期间询问了哪些下游的权威域名服务器,以及他们提供了哪些信息,则很多东西都会非常好理解。</p></blockquote></li></ul><h1 id="了解系统背后发生的事情"><a class="markdownIt-Anchor" href="#了解系统背后发生的事情"></a> 了解系统背后发生的事情</h1><h2 id="获取更详细的dns信息"><a class="markdownIt-Anchor" href="#获取更详细的dns信息"></a> 获取更详细的DNS信息</h2><p>为了让我们可以在获取<code>DNS</code>请求的时候,获取到更多的调试信息,我们可以尝试用<code>dig</code>工具来获取一些信息。一个例子如下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash">dig @223.5.5.5 whatdontexist.lol ] 8:26 PM</span><br><br>; <<>> DiG 9.18.12-0ubuntu0.22.04.2-Ubuntu <<>> @223.5.5.5 whatdontexist.lol<br>; (1 server found)<br>;; global options: +cmd<br>;; Got answer:<br>;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 19819<br>;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0<br><br>;; QUESTION SECTION:<br>;whatdontexist.lol. IN A<br><br>;; AUTHORITY SECTION:<br>lol. 3593 IN SOA ns0.centralnic.net. hostmaster.centralnic.net. 1690719195 900 1800 6048000 3600<br><br>;; Query time: 0 msec<br>;; SERVER: 223.5.5.5#53(223.5.5.5) (UDP)<br>;; WHEN: Sun Jul 30 20:26:45 CST 2023<br>;; MSG SIZE rcvd: 100<br></code></pre></td></tr></table></figure><p>在上面这个示例中,我通过向一个不存在的域名:<code>whatdontexist.lol</code>发起了一个<code>DNS</code>请求。我们可以在这里看到许多有意思的信息,比如我们是对<code>223.5.5.5</code>这个<code>DNS</code>服务器,通过<code>UDP</code>发起的请求等等。</p><h2 id="奇妙的调试信息"><a class="markdownIt-Anchor" href="#奇妙的调试信息"></a> 奇妙的调试信息</h2><p>通过使用<code>dig</code>,我们可以知道很多额外的信息。举个例子,我们可以使用<code>dig +norecurse</code>指令来弄清楚<code>DNS</code>解析服务器目前有没有针对某个特定记录的缓存。对于某个特定的记录,如果不存在缓存,则会返回一个<code>SERVFAIL</code>的状态</p><p>举个例子,我们首先可以向<code>223.5.5.5</code>这个阿里的<code>DNS</code>请求<code>www.baidu.com</code>的<code>DNS</code>条目(大概率来说应该是缓存了百度的域名解析的)</p><p><img src="https://lsky.halc.top/J4Tc62.png" alt="阿里解析百度域名" /></p><p>我们可以看到上图中获取到的<code>status</code>为<code>NOERROR</code>,也就意味着百度在阿里云的<code>DNS</code>服务器中可以正常返回需要的结果。</p><p>现在我们再尝试请求一个不是很常见的域名<code>homestarrunner.com</code>,我们可以发现<code>status</code>变成了<code>SERVFAIL</code></p><p><img src="https://lsky.halc.top/VLzQ1J.png" alt="请求一个不常见的域名" /></p><p>这里的<code>SERVFAIL</code>并不代表不存在对于<code>homestarrunner.com</code>的域名解析,只是在阿里云的服务器中并没有缓存这个域名而已。</p><p>不过对于上面这一长串由<code>dig</code>提供的调试信息,如果我们不是经常和它打交道的话看着还是会有点迷惑。比如:</p><ol><li><code>->>HEADER<<-</code>,<code>flags:</code>,<code>OPT PSEUDOSECTION:</code>,<code>QUESTION SECTION:</code>,<code>MSG SIZE rcvd: 59</code>这些都是啥玩意?</li><li>为什么有的地方看起来是新内容但没换行(比如<code>OPT PSEUDOSECTION:</code>和<code>QUESTION SECTION:</code>)?</li><li>为什么有的返回报文中<code>MSG SIZE rcvd: 59</code>这里是<code>59</code>,而有的报文又是<code>90</code>?不同的报文之间是有啥不同的字段吗?</li></ol><p>总之,从上面的一些问题中,我们可以发现<code>dig</code>的一些输出内容有点像是某些人临时写的一个用于获取这些信息的脚本,而并非有意为了可读性进行一些刻意的设计,为此我们有的时候需要查阅文档来搞懂发生了什么。</p><h3 id="一些小工具"><a class="markdownIt-Anchor" href="#一些小工具"></a> 一些小工具</h3><ol><li><p>这里有一篇文章是原博客作者介绍了如何使用<code>dig</code>的:<a href="https://jvns.ca/blog/2021/12/04/how-to-use-dig/">How to use dig (jvns.ca)</a></p></li><li><p>还有三个工具可以用于更友好的进行一些简单的调试:</p><ul><li><a href="https://github.com/ogham/dog">ogham/dog: A command-line DNS client. (github.com)</a></li><li><a href="https://github.com/mr-karan/doggo">mr-karan/doggo: 🐶 Command-line DNS Client for Humans. Written in Golang (github.com)</a></li><li><a href="https://dns-lookup.jvns.ca/">a simple DNS lookup tool (jvns.ca)</a></li></ul><p>不过对于这些工具有的时候会缺少一些高级功能,比如在原博客发布的时候<code>dog</code>和<code>doggo</code>都还不支持<code>+norecurse</code>这样的功能。所以有的时候学会<code>dig</code>还是有用的</p></li><li><p>通过添加一个<code>+yaml</code>的参数,可以让<code>dig</code>的输出信息更加格式化。不过这样的缺点是返回的信息有点太多了。</p></li></ol><h1 id="小心踩坑"><a class="markdownIt-Anchor" href="#小心踩坑"></a> 小心踩坑</h1><p>在<code>DNS</code>请求中,总是会出现一些奇奇怪怪但是容易不小心就掉进去的陷进。</p><blockquote><p>一些更常见的问题可以翻阅这篇博客:<a href="https://jvns.ca/blog/2022/01/15/some-ways-dns-can-break/">Some ways DNS can break (jvns.ca)</a></p></blockquote><ul><li><p>被动缓存:由于<code>DNS</code>会记录<strong>不存在的域名</strong>,因此假设你在类似<code>cloudflare</code>一类的平台添加自己的<code>DNS</code>解析之前,如果先在自己电脑上访问并被系统缓存了"该域名不存在解析条目"这个结果,即使你后面为这个域名添加了有效的记录,但也要等到之前那条缓存失效了,新的有效结果才能被识别到。</p></li><li><p>在不同平台上对于<code>getaddrinfo</code>的实现并不相同,比如在今年之前,你是没法通过<code>tcp</code>来在<code>musl</code>平台上发起一个<code>dns</code>请求的。</p></li><li><p>有的解析服务器并不尊重解析本应该有的<code>TTL</code>,比如原本你设置了一个域名<code>abc.com</code>的<code>TTL</code>为一分钟或者两分钟。但是路由器认为大部分网络服务都是稳定的(对于非开发人员来说其实倒也是),有可能就会忽略这一点,而硬给你设置一个比如一两个小时的<code>TTL</code>。结果就会导致本来两分钟就应该生效的修改过了一两个小时都没好</p></li><li><p>在<code>Nginx</code>中,如果你按下面的方式配置了一个反向代理</p><figure class="highlight nginx"><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><code class="hljs nginx"><span class="hljs-section">location</span> / {<br> <span class="hljs-attribute">proxy_pass</span> https://some.domain.com;<br>}<br></code></pre></td></tr></table></figure><p>那么<code>Nginx</code>只会在第一次启动的时候尝试解析这个域名,之后则再也不会进行解析。如果你在这个过程中修改了<code>some.domain.com</code>域名指向的<code>IP</code>,就很有可能会出现一些不应该出现的问题。</p><blockquote><p>这个问题实际上也有一些解决方案,不过不是这篇博客的重点</p></blockquote></li><li><p><code>ndonts</code>会使得<code>k8s</code>中的<code>DNS</code>请求变慢:<a href="https://pracucci.com/kubernetes-dns-resolution-ndots-options-and-why-it-may-affect-application-performances.html">Kubernetes pods /etc/resolv.conf ndots:5 option and why it may negatively affect your application performances (pracucci.com)</a></p><blockquote><p>由于我自己对<code>k8s</code>目前接触不多,因此这里只是贴了一个链接。等以后有接触以后再回过头来研究研究</p></blockquote></li></ul><p>在<code>DNS</code>上踩坑往往可能是一件不起眼,但是遇到了就挺难排查的问题。也许最好的解决方案还是尽可能的去见识下别人遇到的问题。再放一次作者记录的常见问题:<a href="https://jvns.ca/blog/2022/01/15/some-ways-dns-can-break/">Some ways DNS can break (jvns.ca)</a></p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>DNS</tag>
</tags>
</entry>
<entry>
<title>网络抓包记录</title>
<link href="/p/3930e42b.html"/>
<url>/p/3930e42b.html</url>
<content type="html"><![CDATA[<h1 id="学习记录"><a class="markdownIt-Anchor" href="#学习记录"></a> 学习记录</h1><p>这个博客主要记录了自己尝试通过抓包分析并解决一些问题的心路历程,从结果上来说很可能问题并没有解决,但是尝试解决这个问题的过程中遇到的一些问题以及自己的思考想通过写博客的方式先记录下来,在以后自己知识储备扩充的时候也许就可以回过头来看看解决下。</p><h1 id="第一次抓包"><a class="markdownIt-Anchor" href="#第一次抓包"></a> 第一次抓包</h1><h2 id="遇到的问题"><a class="markdownIt-Anchor" href="#遇到的问题"></a> 遇到的问题</h2><p>今天在尝试部署<code>zerotier</code>的<code>zeronsd</code>私有<code>DNS</code>服务的时候遇到了一个问题:无论是在我之前国内的服务器A上还是香港的服务器B上,<code>zeronsd</code>的部署都是只需要无脑复制粘贴指令就能成功,但是今天尝试在新租赁的国内服务器C上部署的时候则遇到了一个报错:<code>Error Response</code></p><p><img src="https://lsky.halc.top/wEbLzt.png" alt="不明所以的报错" /></p><p>在翻阅<code>zeronsd</code>源码的时候发现这块逻辑本来应该是对应请求<code>zerotier</code>那边获取到局域网内所有设备的<code>IP</code>以便于创建私有的<code>DNS</code>条目。</p><p><img src="https://lsky.halc.top/kIq3eR.png" alt="zeronsd源码" /></p><p>可以看见这部分错误处理里面并没有<code>Error Response</code>的产生原因,而且同时我在香港的服务器上依旧可以正常使用<code>zeronsd</code>。因此也产生了想尝试通过抓包找到问题所在的想法。</p><h2 id="解决过程"><a class="markdownIt-Anchor" href="#解决过程"></a> 解决过程</h2><h3 id="通过lsof获取目标ip"><a class="markdownIt-Anchor" href="#通过lsof获取目标ip"></a> 通过<code>lsof</code>获取目标IP</h3><p>既然知道了问题是来自于<code>zeronsd</code>,而且应该是一个和网络<code>Response</code>有关系的问题,那么通过抓包应该是最通用的排查方法。在这里首先通过<code>lsof</code>工具查询<code>zeronsd</code>打开的连接</p><blockquote><p>最开始找到的指令是先通过<code>pidof zeronsd</code>找到进程的<code>pid</code>,然后通过<code>pid</code>来用<code>lsof</code>查询,指令大概是<code>lsof -p $(pidof zeronsd)</code>,结果后面翻了下<code>lsof</code>的手册,发现可以直接用<code>-c</code>来找进程,不过<code>pidof</code>以后也感觉会用到,姑且做个记录。</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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></pre></td><td class="code"><pre><code class="hljs shell">[root@Aliyun:~]<br><span class="hljs-meta prompt_"># </span><span class="language-bash">lsof -c zeronsd</span><br>COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME<br>zeronsd 18437 root cwd DIR 254,1 4096 2 /<br>zeronsd 18437 root rtd DIR 254,1 4096 2 /<br>zeronsd 18437 root txt REG 254,1 8752136 804251 /usr/bin/zeronsd<br>zeronsd 18437 root mem REG 254,1 93000 787112 /usr/lib/x86_64-linux-gnu/libresolv-2.31.so<br>zeronsd 18437 root mem REG 254,1 26952 787106 /usr/lib/x86_64-linux-gnu/libnss_dns-2.31.so<br>zeronsd 18437 root mem REG 254,1 51696 787107 /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so<br>zeronsd 18437 root mem REG 254,1 1901536 786465 /usr/lib/x86_64-linux-gnu/libc-2.31.so<br>zeronsd 18437 root mem REG 254,1 18688 787097 /usr/lib/x86_64-linux-gnu/libdl-2.31.so<br>zeronsd 18437 root mem REG 254,1 1321344 787098 /usr/lib/x86_64-linux-gnu/libm-2.31.so<br>zeronsd 18437 root mem REG 254,1 149520 787110 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so<br>zeronsd 18437 root mem REG 254,1 100736 786450 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1<br>zeronsd 18437 root mem REG 254,1 3076992 802235 /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1<br>zeronsd 18437 root mem REG 254,1 597792 802236 /usr/lib/x86_64-linux-gnu/libssl.so.1.1<br>zeronsd 18437 root mem REG 254,1 177928 786460 /usr/lib/x86_64-linux-gnu/ld-2.31.so<br>zeronsd 18437 root 0r CHR 1,3 0t0 4 /dev/null<br>zeronsd 18437 root 1u unix 0x0000000070328a2d 0t0 105666 type=STREAM<br>zeronsd 18437 root 2u unix 0x0000000070328a2d 0t0 105666 type=STREAM<br>zeronsd 18437 root 3u a_inode 0,13 0 8321 [eventpoll]<br>zeronsd 18437 root 4u a_inode 0,13 0 8321 [eventfd]<br>zeronsd 18437 root 5u a_inode 0,13 0 8321 [eventpoll]<br>zeronsd 18437 root 6u unix 0x000000008c202115 0t0 105669 type=STREAM<br>zeronsd 18437 root 7u unix 0x0000000072325ef6 0t0 105670 type=STREAM<br>zeronsd 18437 root 8u unix 0x000000008c202115 0t0 105669 type=STREAM<br>zeronsd 18437 root 9u IPv4 104709 0t0 TCP iZf8zgk9dawv2exr28bz0oZ:57136->151.101.109.91:https (SYN_SENT)<br></code></pre></td></tr></table></figure><p>可以发现下面有创建一个<code>TCP</code>连接,并且是处于<code>SYN_SENT</code>的状态。同时在我短时间内重复输入<code>lsof</code>的指令(蠢但有效.jpg),发现返回的依旧是<code>SYN_SENT</code>。一般来说<code>SYN</code>握手的速度应该是很快的,这里很长时间内都处于<code>SYN_SENT</code>的状态就明显很不对劲,所以接下来就需要通过抓包来分析。</p><p>在我多次尝试了<code>lsof</code>之后,发现即使有的时候能让连接处于<code>ESTABLISHED</code>的状态,但是过了一会以后依旧会被掐断,并且返回<code>Error Response</code>。通过分析不同的<code>lsof</code>建立<code>TCP</code>连接的目标<code>ip</code>,发现服务器总是向<code>151.101.109.91</code>和<code>146.75.113.91</code>建立连接,应该是请求的域名有做<code>CDN</code>所以解析到了不同的<code>IP</code>。接下来知道了目标<code>IP</code>,抓包就很容易了。这里采取的是通过<code>tshark</code>在配置较弱的服务器上获取到了数据包以后再导出到本地计算机的<code>Wireshark</code>的方法进行分析。</p><h3 id="tshark抓包分析"><a class="markdownIt-Anchor" href="#tshark抓包分析"></a> <code>tshark</code>抓包分析</h3><p>首先使用下面的指令对所有<code>http</code>请求进行抓取,并将抓包的内容保存在<code>data.cap</code>文件中</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">tshark -d tcp.port==443,http -w data.cap<br></code></pre></td></tr></table></figure><p>之后再将这个文件下载到本地电脑使用<code>Wireshark</code>过滤选定目标<code>IP</code>追踪<code>TCP</code>流,可以发现数据流如下图</p><p><img src="https://lsky.halc.top/Fm7IOM.png" alt="数据流" /></p><p>可以发现就这一次的数据来说<code>SYN</code>握手的部分是成功了的,<code>TLS</code>的四次握手也能成功建立,但是在发送了一些应用数据之后,云的服务器就开始向远端服务器发送<code>RST</code>报文来请求强制终止连接了。重新抓包以后又发现出现了多次<code>TCP</code>数据包的重传。</p><p>对于在TCP连接中,先发送了<code>FIN</code>,然后发送<code>RST</code>的一个可能性的原因如下:</p><ul><li><a href="https://serverfault.com/questions/854692/client-sends-rst-after-fin-ack">packet capture - Client sends RST after FIN,ACK - Server Fault</a></li></ul><blockquote><p>您的流中的FIN和RST数据包并不直接相关。通过发送FIN,表示没有更多要发送的数据。它仍然可以从连接的另一端接收更多数据。然而,当有更多数据到达时,发送RST来表示应用程序将不再从套接字读取任何数据。</p><p>如果一个应用程序想要干净地关闭TCP连接而不引发任何RST数据包,则必须首先使用shutdown系统调用关闭写入套接字,同时保持读取套接字处于打开状态。在关闭写入套接字之后,它仍然需要读取所有对方要发送的数据,然后才能完全关闭套接字。</p></blockquote><p>但是<code>zeronsd</code>对于这次连接的重置是返回了<code>Error</code>的,所以基本上可以判断并不是<code>zeronsd</code>本身发送的<code>rst</code>阻断连接。</p><p>在这里为了让后续抓包更容易复现,首先对于<code>TLS</code>建立握手的第一个数据包,我们可以直接查询到域名</p><img src="https://lsky.halc.top/3bRIeE.png" alt="检查TLS握手的域名" style="zoom:50%;" /><p>之后我就尝试通过<code>curl -vL my.zerotier.com</code>的方法来通过抓取<code>curl</code>包分析问题。为了区分成功和失败的区别,我在这里也使用了<code>curl -vL www.baidu.com</code>作为对照组。</p><h4 id="百度请求"><a class="markdownIt-Anchor" href="#百度请求"></a> 百度请求</h4><p>返回的抓包内容大致如下:</p><p><img src="https://lsky.halc.top/DKeJLu.png" alt="使用curl请求百度" /></p><p>可以发现整个连接没有大问题,只是在连接结束以后百度那边发送了一个<code>rst</code>包给我们,但是<code>tcp</code>的四次挥手是正常完成了的。</p><h4 id="zerotier请求"><a class="markdownIt-Anchor" href="#zerotier请求"></a> Zerotier请求</h4><p><img src="https://lsky.halc.top/5fRpjw.png" alt="使用curl请求zerotier" /></p><p>可以发现在想要结束连接的时候并没有正常挥手,在客户端这边接收到服务器那边的<code>FIN</code>之前就开始给服务器发送<code>RST</code>报文尝试断开连接</p><h2 id="总述"><a class="markdownIt-Anchor" href="#总述"></a> 总述</h2><p>这次抓包本身没有获取到啥决定性的信息可以确定问题产生的原因,因此目前自己也只能借由<code>Zerotier</code>和百度请求的对比,怀疑是因为<code>Zerotier</code>是国外网站,被服务商阻断了(但是在<code>FIN</code>之后再阻断也很奇怪,虽然会在<code>FIN</code>之后发送<code>RST</code>报文,但是<code>curl</code>还是可以读取到<code>my.zerotier.com</code>的网页信息),暂时对进一步的排查没有头绪。</p><h1 id="第二次遇到问题"><a class="markdownIt-Anchor" href="#第二次遇到问题"></a> 第二次遇到问题</h1><p>我尝试在同一台服务器上部署一个<code>VOIP</code>服务器。也是一样使用别的服务器的时候一点问题都没有,但是在这台国内的服务器上就遇到问题了。这次遇到问题就想尽可能的搞懂原因,因此做了以下实验</p><h2 id="分情况尝试连接"><a class="markdownIt-Anchor" href="#分情况尝试连接"></a> 分情况尝试连接</h2><ul><li><p>实验设备:一台香港服务器(对照组),一台阿里云国内服务器(样本组),以及自己的<code>Windows</code>设备</p></li><li><p>实验情景:在香港和国内服务器上都部署<code>Mumble</code>的服务器(一个<code>VOIP</code>程序),然后使用<code>Windows</code>对这两台服务器进行连接,其中<code>Mumble</code>连接采取了<code>TLS</code>加密的方式,加密证书均为自签。</p></li><li><p>实验步骤:</p><ol><li>使用IP直连香港服务器上在端口<code>64738</code>部署的<code>murmur</code>(<code>Mumble</code>服务端的别称)</li><li>使用域名<code>voice1.abc.com</code>连接香港服务器在端口<code>64738</code>部署的<code>murmur</code></li><li>使用IP直连国内服务器上在端口<code>64738</code>部署的<code>murmur</code></li><li>使用域名<code>voice2.abc.com</code>连接国内服务器在端口<code>64738</code>部署的<code>murmur</code></li></ol></li><li><p>实验期望:四种不同的方式连接<code>murmur</code>都能成功,且不会有明显区别</p></li><li><p>实验结果:方法1-3都可以正常访问,但是方法4连接被服务器阻断</p></li></ul><h2 id="抓包分析问题"><a class="markdownIt-Anchor" href="#抓包分析问题"></a> 抓包分析问题</h2><p>在对以上四种情况进行抓包以后,获取到的<code>Wireshark</code>图像大致如下</p><ol><li><p>香港服务器使用域名连接</p><p><img src="https://lsky.halc.top/2nYkkM.png" alt="香港 - 域名连接" /></p></li><li><p>香港服务器使用IP直连</p><p><img src="https://lsky.halc.top/DzjxGf.png" alt="香港 - IP直连" /></p></li><li><p>国内服务器使用IP直连</p><p><img src="https://lsky.halc.top/oq3GCR.png" alt="国内 - IP直连" /></p></li><li><p>国内服务器使用域名连接</p><p><img src="https://lsky.halc.top/BOOb7K.png" alt="国内 - 域名连接" /></p></li></ol><p>到这里基本上很明显可以发现只有在国内服务器使用域名连接的时候,服务器那边会在进行<code>TLS</code>握手的时候直接进行阻断,让你无法成功建立<code>TLS</code>连接</p><h2 id="总述-2"><a class="markdownIt-Anchor" href="#总述-2"></a> 总述</h2><p>这次抓包也没获得啥特别有用的信息。不过由于这个问题是在用阿里云的时候才遇到,之前用同样在国内的腾讯云没有遇到,则初步怀疑是阿里云对任意端口(非443)现在都做了备案检测,只要是没备案的域名/网站,无论是<code>TLS</code>还是明文,只要检测到你用了域名就禁封。相比之下腾讯那边就要宽松一些,至少<code>murmur</code>在腾讯云上是可以正常使用域名进行连接通讯的。</p>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>Wireshark</tag>
</tags>
</entry>
<entry>
<title>基础算法(一)</title>
<link href="/p/83fa91fc.html"/>
<url>/p/83fa91fc.html</url>
<content type="html"><![CDATA[<h1 id="基础算法一"><a class="markdownIt-Anchor" href="#基础算法一"></a> 基础算法(一)</h1><h2 id="快速排序"><a class="markdownIt-Anchor" href="#快速排序"></a> 快速排序</h2><h3 id="快排"><a class="markdownIt-Anchor" href="#快排"></a> 快排</h3><blockquote><p>题目链接:<a href="https://www.acwing.com/problem/content/787/">785. 快速排序 - AcWing题库</a></p></blockquote><p>快排的主要思想是基于分治</p><h4 id="找到分界点"><a class="markdownIt-Anchor" href="#找到分界点"></a> 找到分界点</h4><p>对于一整串数组,首先找到一个值作为分界点。分界点的取值有三种取值方法:</p><ul><li>取区间的左边界</li><li>取区间的中间位置的值</li><li>随机取一个位置</li></ul><h4 id="调整区间"><a class="markdownIt-Anchor" href="#调整区间"></a> 调整区间</h4><p>让分界点(设为x)前面的区间部分全都是小于等于x的值,数组后面的部分则都是大于等于x的部分。</p><h4 id="递归处理左右两段"><a class="markdownIt-Anchor" href="#递归处理左右两段"></a> 递归处理左右两段</h4><p>再对区间的左和右分别进行排序,只要两侧都成功排序那么整个区间就完成了排序。</p><hr /><p>该问题在处理的过程中主要的操作就是调整区间。并且最后的效果是让区间处于了两种互斥的不同状态。因此可以用双指针的做法,同时从前和末端向中间进行扫描,当他们一方扫描到需要进行交换的异端分子的时候,就等待另一端也扫描出同样的异端分子。当双方都扫描到对方的异端分子的时候,只需要将这两个异端分子同时交换,当两个指针相遇的时候,也就是处理好了所有异端分子的时候。</p><p>模板实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">quick_sort</span><span class="hljs-params">(<span class="hljs-type">int</span> q[], <span class="hljs-type">int</span> l, <span class="hljs-type">int</span> r)</span> </span>{<br> <span class="hljs-keyword">if</span> (l >= r)<br> <span class="hljs-keyword">return</span>;<br> <span class="hljs-type">int</span> x = q[(l + r) / <span class="hljs-number">2</span>], i = l - <span class="hljs-number">1</span>, j = r + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span> (i < j) {<br> <span class="hljs-keyword">do</span><br> i++;<br> <span class="hljs-keyword">while</span> (q[i] < x);<br> <span class="hljs-keyword">do</span><br> j--;<br> <span class="hljs-keyword">while</span> (q[j] > x);<br> <span class="hljs-keyword">if</span> (i < j)<br> <span class="hljs-built_in">swap</span>(q[i], q[j]);<br> }<br> <span class="hljs-built_in">quick_sort</span>(q, l, j), <span class="hljs-built_in">quick_sort</span>(q, j + <span class="hljs-number">1</span>, r);<br>}<br></code></pre></td></tr></table></figure><h3 id="第k个数"><a class="markdownIt-Anchor" href="#第k个数"></a> 第k个数</h3><blockquote><p>题目链接:<a href="https://www.acwing.com/problem/content/788/">786. 第k个数 - AcWing题库</a></p></blockquote><h4 id="找到分界点-选取区间"><a class="markdownIt-Anchor" href="#找到分界点-选取区间"></a> 找到分界点、选取区间</h4><p>分界点的选取和快排相同。不同的是由于我们这里只需要第k小的数,因此在此时对划分出来的区间长度进行判断。如果k的大小小于左区间长度l,那么说明k在左区间,继续从左区间寻找第k小的数。如果k的大小大于l,说明k在右区间,在右区间寻找第(k - l)小的数。</p><hr /><p>代码实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">k_sort</span><span class="hljs-params">(<span class="hljs-type">int</span> l, <span class="hljs-type">int</span> r, <span class="hljs-type">int</span> k)</span> </span>{<br> <span class="hljs-keyword">if</span> (l >= r)<br> <span class="hljs-keyword">return</span> q[l];<br> <span class="hljs-type">int</span> x = q[(l + r) >> <span class="hljs-number">1</span>], i = l - <span class="hljs-number">1</span>, j = r + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span> (i < j) {<br> <span class="hljs-keyword">do</span><br> i++;<br> <span class="hljs-keyword">while</span> (q[i] < x);<br> <span class="hljs-keyword">do</span><br> j--;<br> <span class="hljs-keyword">while</span> (q[j] > x);<br> <span class="hljs-keyword">if</span> (i < j)<br> <span class="hljs-built_in">swap</span>(q[i], q[j]);<br> }<br><br> <span class="hljs-type">int</span> sl = j - l + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span> (k <= sl)<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">k_sort</span>(l, j, k);<br> <span class="hljs-keyword">else</span><br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">k_sort</span>(j + <span class="hljs-number">1</span>, r, k - sl);<br>}<br></code></pre></td></tr></table></figure><h2 id="归并排序"><a class="markdownIt-Anchor" href="#归并排序"></a> 归并排序</h2><blockquote><p>题目链接:<a href="https://www.acwing.com/problem/content/789/">787. 归并排序 - AcWing题库</a></p></blockquote><ol><li>确定分界点:<code>mid = (l + r) / 2</code></li><li>分别递归排序左区间和右区间</li><li>将两个数组合并</li></ol><h3 id="双指针合并"><a class="markdownIt-Anchor" href="#双指针合并"></a> 双指针合并</h3><p>归并排序的主要思路就是将原本一个大数组,使用分治的思想,从单个数字的小数组进行不断的归并,最后获得的就是一个有序的新数组。因此主要的操作也就是在合并的这个操作上。</p><p>我们需要合并的数组有两个,因此这部分只需要用两个数组分别指向这两个数组的开头。然后再创建一个临时数组用于存放归并的结果。归并的过程中只需要每次都将两个指针中最小的那个输入加入临时数组中,然后将存入的指针后移,直到两个数组中其中一个被归并完毕,再将另外一个数组后面所有的结果合入答案的临时数组,最后将临时数组的结果写入原数组中即可。</p><hr /><p>模板实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">merge_sort</span><span class="hljs-params">(<span class="hljs-type">int</span> q[], <span class="hljs-type">int</span> l, <span class="hljs-type">int</span> r)</span> </span>{<br> <span class="hljs-keyword">if</span> (l >= r)<br> <span class="hljs-keyword">return</span>;<br> <span class="hljs-comment">// 将区间分成左右两边,归并合并</span><br> <span class="hljs-type">int</span> mid = (l + r) >> <span class="hljs-number">1</span>;<br> <span class="hljs-built_in">merge_sort</span>(q, l, mid), <span class="hljs-built_in">merge_sort</span>(q, mid + <span class="hljs-number">1</span>, r);<br> <span class="hljs-type">int</span> merged = <span class="hljs-number">0</span>, i = l, j = mid + <span class="hljs-number">1</span>;<br> <span class="hljs-comment">// 使用一个tmp的临时数组来存储归并后的结果</span><br> <span class="hljs-keyword">while</span> (i <= mid && j <= r) {<br> <span class="hljs-keyword">if</span> (q[i] <= q[j]) {<br> tmp[merged++] = q[i++];<br> } <span class="hljs-keyword">else</span> {<br> tmp[merged++] = q[j++];<br> }<br> }<br> <span class="hljs-comment">// 将多余结尾的部分插入tmp当中</span><br> <span class="hljs-keyword">while</span> (i <= mid) {<br> tmp[merged++] = q[i++];<br> }<br> <span class="hljs-keyword">while</span> (j <= r) {<br> tmp[merged++] = q[j++];<br> }<br> <span class="hljs-comment">// 将tmp合并好的数组返回输入给q[]中</span><br> <span class="hljs-keyword">for</span> (i = l, j = <span class="hljs-number">0</span>; i <= r; i++, j++) {<br> q[i] = tmp[j];<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="求逆序对的数量"><a class="markdownIt-Anchor" href="#求逆序对的数量"></a> 求逆序对的数量</h3><blockquote><p>题目链接:<a href="https://www.acwing.com/problem/content/790/">788. 逆序对的数量 - AcWing题库</a></p></blockquote><blockquote><p>逆序对:5 2 2 1 4,只要前面一个数比后面一个数字大,即为一个逆序对,因此有[5, 2], [5, 2], [2, 1], [5, 1], [5, 4]。这五个逆序对</p></blockquote><p>首先,这个问题可以在对一个区间对半切割以后分为三种情况</p><p><img src="https://lsky.halc.top/bNkSHs.png" alt="区间分类" /></p><ul><li>在左区间中存在两个数字是逆序对</li><li>在右区间中存在两个数字是逆序对</li><li>在中间的两个黄色中,左区间存在一个数字是右区间的逆序对</li></ul><p>其次,在这里引入归并排序的思想。在归并排序中,对于整个区间的排序本质上是对于最小区间(两个数字)之间的大小比较和扶正,最后扩展为整个区间的大小比较和扶正(分治)。带入到这个问题中,其实就是首先视 <strong>第三种情况</strong> 为最小的情况,然后最后的所有结果其实都是第三种情况的总和,所谓的第一种情况和第二种情况将会在最小区间的过程中被直接统计进入结果当中,也就是说我们只需要求出所有第三种情况逆序对的数量再加起来就是最后答案。</p><h4 id="对于左右区间逆序对数量的判断"><a class="markdownIt-Anchor" href="#对于左右区间逆序对数量的判断"></a> 对于左右区间逆序对数量的判断</h4><p><img src="https://lsky.halc.top/ZNhDJ8.png" alt="逆序对的数量计算" /></p><p>目前我们只考虑黄色的情况,因此对于一个区间,我们可以分成<code>p</code>和<code>q</code>两个部分来考虑。假设在<code>p</code>和<code>q</code>上有符合归并排序的两个指针i和j,且当前的情况符合了逆序对的<code>p[i] > q[j]</code>的定义。此时我们可以很容易就知道从i到mid这整个区间的数字都是大于q[j]的,而这个区间内数字的数量为<code>mid - i + 1</code>。通过这个规律,我们就可以知道如果我们想要统计所有的黄色情况中逆序对的数量,我们只需要将所有符合<code>p[i] > q[j]</code>情况的<code>mid - i + 1</code>数量加起来,就是最后答案。</p><hr /><p>代码实现:</p><blockquote><p>关于计算逆序对的数量问题,假设总共有n个数据,由于每两个数据是一组,从<code>n</code>和<code>n-1</code>可以为一组的情况下来考虑,最后总共可以有(n(n - 1))/2大小的答案,如果数据集到达了类似100000量级的时候,最后答案会超过int的范围,因此有可能需要使用<code>long long</code></p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-type">const</span> <span class="hljs-type">int</span> N = <span class="hljs-number">100010</span>;<br><span class="hljs-type">int</span> tmp[N], q[N], n;<br><br><span class="hljs-function"><span class="hljs-type">long</span> <span class="hljs-type">long</span> <span class="hljs-title">count_pair</span><span class="hljs-params">(<span class="hljs-type">int</span> l, <span class="hljs-type">int</span> r)</span> </span>{<br> <span class="hljs-keyword">if</span> (l >= r)<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> <span class="hljs-type">int</span> mid = (l + r) >> <span class="hljs-number">1</span>;<br> <span class="hljs-comment">// 将左区间和右区间分治的结果加起来</span><br> <span class="hljs-type">long</span> <span class="hljs-type">long</span> res = <span class="hljs-built_in">merge_sort</span>(l, mid) + <span class="hljs-built_in">merge_sort</span>(mid + <span class="hljs-number">1</span>, r);<br> <span class="hljs-type">int</span> merged = <span class="hljs-number">0</span>, i = l, j = mid + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span> (i <= mid && j <= r) {<br> <span class="hljs-keyword">if</span> (q[i] <= q[j]) {<br> <span class="hljs-comment">// 不符合逆序对,直接归并</span><br> tmp[merged++] = q[i++];<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">// 符合逆序对的定义,归并的同时统计结果数量</span><br> tmp[merged++] = q[j++];<br> res += mid - i + <span class="hljs-number">1</span>;<br> }<br> }<br> <span class="hljs-comment">// 将归并以后长的部分合并</span><br> <span class="hljs-keyword">while</span> (i <= mid) {<br> tmp[merged++] = q[i++];<br> }<br> <span class="hljs-keyword">while</span> (j <= r) {<br> tmp[merged++] = q[j++];<br> }<br> <span class="hljs-comment">// 将排序以后的数组恢复到q[]内</span><br> <span class="hljs-keyword">for</span> (i = l, j = <span class="hljs-number">0</span>; i <= r; i++, j++) {<br> q[i] = tmp[j];<br> }<br> <span class="hljs-keyword">return</span> res;<br>}<br></code></pre></td></tr></table></figure><h2 id="二分"><a class="markdownIt-Anchor" href="#二分"></a> 二分</h2><p>二分算法的本质为在一个区间中,存在一个位置使得区间的性质发生了变化,进而来寻找这个变化的点。</p><p><img src="https://lsky.halc.top/qqbq2L.png" alt="二分示意图" /></p><p>以上面这个图为例,对于红色区间和绿色区间,假设他们有不同的性质,且一个以A作为分界点,一个以B作为分界点。那么在使用二分的时候就有两种考虑</p><h3 id="二分的分类讨论"><a class="markdownIt-Anchor" href="#二分的分类讨论"></a> 二分的分类讨论</h3><p>在分类之前,首先对于所有的二分情况都有一个<code>check()</code>函数,用于判断某个点是否符合某个状态。在这里我们假设为某个点是否符合某个颜色(红/绿)区间的范围内</p><h4 id="红色区间"><a class="markdownIt-Anchor" href="#红色区间"></a> 红色区间</h4><h5 id="区间左边界右移"><a class="markdownIt-Anchor" href="#区间左边界右移"></a> 区间左边界右移</h5><p>如果我们需要使用二分法来取得A点的位置,那么假设我们先设了中点<code>mid=(l + r)/2</code>,那么就有两种情况。第一种情况是<code>mid</code>处于红色范围内,那么我们便很容易可以知道点<code>A</code>一定在<code>mid</code>到<code>r</code>之间</p><p><img src="https://lsky.halc.top/F6D4Iq.png" alt="分类一" /></p><p>此时我们只需要有新的<code>l = mid</code>,然后从<code>l</code>到<code>r</code>中再次进行二分,直到<code>l</code>和<code>r</code>不为<code>l < r</code>的关系即可</p><h5 id="区间右边界左移"><a class="markdownIt-Anchor" href="#区间右边界左移"></a> 区间右边界左移</h5><p>和上图相反,如果我们是<code>mid</code>处于了绿色范围中,那么我们首先可以知道的是,<code>mid</code>这个点自身是不符合红色区间的范围的。因此我们也只需要有新的<code>r = mid - 1</code>即可。</p><hr /><p>模板实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">bsearch_left</span><span class="hljs-params">(<span class="hljs-type">int</span> l, <span class="hljs-type">int</span> r)</span> </span>{<br> <span class="hljs-keyword">while</span> (l < r) {<br> <span class="hljs-comment">// 由于r是以mid - 1来进行更新移动,因此如果没有+ 1的话将会出现死循环</span><br> <span class="hljs-type">int</span> mid = (l + r + <span class="hljs-number">1</span>) >> <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">check</span>(mid)) {<br> l = mid;<br> } <span class="hljs-keyword">else</span> {<br> r = mid - <span class="hljs-number">1</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> l;<br>}<br></code></pre></td></tr></table></figure><h4 id="绿色区间"><a class="markdownIt-Anchor" href="#绿色区间"></a> 绿色区间</h4><p>绿色区间和红色区间主要思路完全相同,只有区间在移动边界的时候条件不同。当需要右移区间的时候,有<code>l = mid + 1</code>,而区间如果要左移,只需要<code>r = mid</code>即可。因为这里这里不存在<code>mid</code>当区间长度为2的时候,如果右移区间会死循环的问题,因此<code>mid</code>直接取<code>(l + r) >> 1</code>即可。</p><hr /><p>模板实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">binary_search</span><span class="hljs-params">(<span class="hljs-type">int</span> l, <span class="hljs-type">int</span> r)</span> </span>{<br> <span class="hljs-keyword">while</span> (l < r) {<br> <span class="hljs-type">int</span> mid = (l + r) >> <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">check</span>(mid)) {<br> r = mid;<br> } <span class="hljs-keyword">else</span> {<br> l = mid + <span class="hljs-number">1</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> l;<br>}<br></code></pre></td></tr></table></figure><h3 id="数的范围"><a class="markdownIt-Anchor" href="#数的范围"></a> 数的范围</h3><blockquote><p>题目链接:<a href="https://www.acwing.com/problem/content/791/">789. 数的范围 - AcWing题库</a></p></blockquote><p>首先对于二分的题目,首先找出区分***红色区间***和***绿色区间*<strong>的<code>check()</code>函数。在这个题目中,主要目的是找到针对某个数字<code>target</code>,求出在数组中<code>target</code>最小的区间边界和最大的区间边界。因此可以通过</strong>大于等于<code>target</code><strong>和</strong>小于等于<code>target</code>**来写出两个二分的函数,分别用于寻找左边界和右边界的位置</p><hr /><p>代码实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 获取区间左边界</span><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">bsearch_left</span><span class="hljs-params">(<span class="hljs-type">int</span> l, <span class="hljs-type">int</span> r, <span class="hljs-type">int</span> value)</span> </span>{<br> <span class="hljs-keyword">while</span> (l < r) {<br> <span class="hljs-type">int</span> mid = (l + r) >> <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span> (numbers[mid] >= value) {<br> r = mid;<br> } <span class="hljs-keyword">else</span> {<br> l = mid + <span class="hljs-number">1</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> l;<br>}<br><br><span class="hljs-comment">// 获取区间右边界</span><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">bsearch_right</span><span class="hljs-params">(<span class="hljs-type">int</span> l, <span class="hljs-type">int</span> r, <span class="hljs-type">int</span> value)</span> </span>{<br> <span class="hljs-keyword">while</span> (l < r) {<br> <span class="hljs-type">int</span> mid = (l + r + <span class="hljs-number">1</span>) >> <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span> (numbers[mid] <= value) {<br> l = mid;<br> } <span class="hljs-keyword">else</span> {<br> r = mid - <span class="hljs-number">1</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> l;<br>}<br></code></pre></td></tr></table></figure><h2 id="高精度问题"><a class="markdownIt-Anchor" href="#高精度问题"></a> 高精度问题</h2><h3 id="高精度加法"><a class="markdownIt-Anchor" href="#高精度加法"></a> 高精度加法</h3><blockquote><p>题目链接:<a href="https://www.acwing.com/problem/content/793/">791. 高精度加法 - AcWing题库</a></p></blockquote><p>高精度加法本质就是以字符串将数字读入以后,代码模拟手动计算十进制的过程,大于十就进一位。</p><hr /><p>模板实现:</p><blockquote><p>这里一定要注意<code>A[i]</code>或者<code>B[i]</code>是否为数字,如果是字符的话还需要进行<code>- '0'</code>来让结果变成数字</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function">vector<<span class="hljs-type">int</span>> <span class="hljs-title">add</span><span class="hljs-params">(<span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>> &A, <span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>> &B)</span> </span>{<br> vector<<span class="hljs-type">int</span>> C;<br> <span class="hljs-type">int</span> t = <span class="hljs-number">0</span>; <span class="hljs-comment">// 是否进位</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < A.<span class="hljs-built_in">size</span>() || i < B.<span class="hljs-built_in">size</span>(); i++) {<br> <span class="hljs-keyword">if</span> (i < A.<span class="hljs-built_in">size</span>())<br> t += A[i]; <span class="hljs-comment">// 记得确保这里加入的是数字,而不是字符</span><br> <span class="hljs-keyword">if</span> (i < B.<span class="hljs-built_in">size</span>())<br> t += B[i];<br> C.<span class="hljs-built_in">push_back</span>(t % <span class="hljs-number">10</span>);<br> t /= <span class="hljs-number">10</span>;<br> }<br> <span class="hljs-keyword">if</span> (t)<br> C.<span class="hljs-built_in">push_back</span>(<span class="hljs-number">1</span>);<br> <span class="hljs-keyword">return</span> C;<br>}<br></code></pre></td></tr></table></figure><h3 id="高精度减法"><a class="markdownIt-Anchor" href="#高精度减法"></a> 高精度减法</h3><blockquote><p>题目链接:<a href="https://www.acwing.com/problem/content/794/">792. 高精度减法 - AcWing题库</a></p></blockquote><p>高精度减法在实现之前,首先要确定被减项比减去的值要大,如果小的话则要提前分类讨论输出一个负号。</p><hr /><p>判断大小的一个简单实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span>>(<span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>> &rhs, <span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>> &lhs) {<br> <span class="hljs-comment">// 如果是"987",读入的则是"789"。所以只需要从后向前逐步判断</span><br> <span class="hljs-keyword">if</span> (rhs.<span class="hljs-built_in">size</span>() != lhs.<span class="hljs-built_in">size</span>())<br> <span class="hljs-keyword">return</span> rhs.<span class="hljs-built_in">size</span>() > lhs.<span class="hljs-built_in">size</span>();<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = rhs.<span class="hljs-built_in">size</span>() - <span class="hljs-number">1</span>; i >= <span class="hljs-number">0</span>; i--) {<br> <span class="hljs-keyword">if</span> (rhs[i] != lhs[i]) {<br> <span class="hljs-keyword">return</span> rhs[i] > lhs[i];<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br>}<br></code></pre></td></tr></table></figure><hr /><p>对于减法的模拟流程,和加法主要的不同就是借位的操作。借位主要体现在计算第<code>i</code>位的<code>A[i]</code>和<code>B[i]</code>的运算的时候,如果有<code>A[i] - B[i]</code>结果是负数的话,那么<code>A[i]</code>就需要向<code>A[i + 1]</code>进行借位。这个时候我们只需要单独使用一个变量<code>t</code>,如果当前运算结果为负数需要借位了则让<code>t</code>为<code>1</code>,并且在每次运算前让<code>A[i]</code>减去<code>t</code>来实现借位的操作。</p><p>同时在执行完了减法的逻辑之后,由于减法和加法不同,可能会出现"0001"这种数字,我们还需要将所有除了最后一位(因为答案可能为"0")的所有<code>0</code>给去掉。因为通过<code>vector</code>存储的数字是倒序,也就是说"0001"在数组里面是<code>[1, 0, 0, 0]</code>。因此我们只需要每次都把答案的末尾给剔除即可。</p><p>模板实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function">vector<<span class="hljs-type">int</span>> <span class="hljs-title">sub</span><span class="hljs-params">(<span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>> &A, <span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>> &B)</span> </span>{<br> <span class="hljs-comment">// 执行函数前需要先确保传入的A比B大</span><br> vector<<span class="hljs-type">int</span>> ans;<br> <span class="hljs-comment">// 减法的流程</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>, t = <span class="hljs-number">0</span>; i < A.<span class="hljs-built_in">size</span>(); i++) {<br> <span class="hljs-comment">// 判断之前是否进行了借位操作,然后将A[i]的值给t</span><br> t = A[i] - t;<br> <span class="hljs-keyword">if</span> (i < B.<span class="hljs-built_in">size</span>())<br> <span class="hljs-comment">// 使用借位以后的A[i]减去B[i]</span><br> t -= B[i];<br> <span class="hljs-comment">// (t + 10) % 10 让答案处于0-9绝对值的状态</span><br> ans.<span class="hljs-built_in">push_back</span>((t + <span class="hljs-number">10</span>) % <span class="hljs-number">10</span>);<br> <span class="hljs-comment">// 如果相减以后的数字是负数代表需要在下一次操作进行借位</span><br> <span class="hljs-keyword">if</span> (t < <span class="hljs-number">0</span>) {<br> t = <span class="hljs-number">1</span>;<br> } <span class="hljs-keyword">else</span> {<br> t = <span class="hljs-number">0</span>;<br> }<br> }<br> <span class="hljs-comment">// 去掉多余的0</span><br> <span class="hljs-keyword">while</span> (ans.<span class="hljs-built_in">size</span>() > <span class="hljs-number">1</span> && ans.<span class="hljs-built_in">back</span>() == <span class="hljs-number">0</span>)<br> ans.<span class="hljs-built_in">pop_back</span>();<br> <span class="hljs-keyword">return</span> ans;<br>}<br></code></pre></td></tr></table></figure><h3 id="高精度乘法"><a class="markdownIt-Anchor" href="#高精度乘法"></a> 高精度乘法</h3><blockquote><p>题目链接:<a href="https://www.acwing.com/problem/content/795/">793. 高精度乘法 - AcWing题库</a></p></blockquote><p>高精度乘法的主要思路和高精度加法差不多,这类题目通常为一个大整数乘以一个小整数。对于这种情况下的乘法,我们只需要先将大整数和之前一样序列化成一个<code>vector<int></code>的变量,然后和加法一样让容器每一位都和小整数相乘,大于<code>10</code>的部分留给下一位用于进位即可。</p><hr /><p>模板实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function">vector<<span class="hljs-type">int</span>> <span class="hljs-title">mul</span><span class="hljs-params">(<span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>> &A, <span class="hljs-type">int</span> b)</span> </span>{<br> <span class="hljs-comment">// 如果为0的话,最后答案可能会为"00000"这种需要删除多余字符的vector</span><br> <span class="hljs-keyword">if</span> (b == <span class="hljs-number">0</span>) {<br> <span class="hljs-keyword">return</span> {<span class="hljs-number">0</span>};<br> }<br> vector<<span class="hljs-type">int</span>> ans;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>, t = <span class="hljs-number">0</span>; i < A.<span class="hljs-built_in">size</span>() || t; i++) {<br> <span class="hljs-keyword">if</span> (i < A.<span class="hljs-built_in">size</span>())<br> t += A[i] * b;<br> ans.<span class="hljs-built_in">push_back</span>(t % <span class="hljs-number">10</span>);<br> t /= <span class="hljs-number">10</span>;<br> }<br> <span class="hljs-keyword">return</span> ans;<br>}<br></code></pre></td></tr></table></figure><h3 id="高精度除法"><a class="markdownIt-Anchor" href="#高精度除法"></a> 高精度除法</h3><blockquote><p>题目链接:<a href="https://www.acwing.com/problem/content/796/">794. 高精度除法 - AcWing题库</a></p></blockquote><p>高精度除法的题目一般形式为一个大数除以一个小数。此时假设大数是<code>123456789</code>,小数是<code>11</code>。这种情况下按照正常计算逻辑大致如下:</p><p><img src="https://lsky.halc.top/g8dPuG.png" alt="除法" /></p><p>由于在加减乘法中,我们都是将数字以<code>[9, 8, 7, 6, 5, 4, 3, 2, 1]</code>的顺序存储的,因此我们在计算除法的时候需要从<code>A[A.size() - 1]</code>的位置开始正常除法的计算逻辑,直到<code>A[0]</code>。其中在每次除的过程中,假设经过上次运算(默认的<code>r = 0</code>)的<code>r</code>是<code>r'</code>,那么在下一次计算的时候用于计算的余数则是<code>r = r' + A[i]</code>,然后只需要将除数放入<code>ans</code>的数组中,然后余数继续留给下一次计算即可。</p><hr /><p>模板实现:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function">vector<<span class="hljs-type">int</span>> <span class="hljs-title">div</span><span class="hljs-params">(<span class="hljs-type">const</span> vector<<span class="hljs-type">int</span>> &A, <span class="hljs-type">int</span> diver, <span class="hljs-type">int</span> &reminder)</span> </span>{<br> vector<<span class="hljs-type">int</span>> ans;<br> reminder = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = A.<span class="hljs-built_in">size</span>() - <span class="hljs-number">1</span>; i >= <span class="hljs-number">0</span>; i--) {<br> reminder = reminder * <span class="hljs-number">10</span> + A[i];<br> ans.<span class="hljs-built_in">push_back</span>(reminder / diver);<br> reminder = reminder % diver;<br> }<br> <span class="hljs-comment">// 由于除法是正常顺序进行计算,因此需要将答案反转以后去掉前导0</span><br> <span class="hljs-built_in">reverse</span>(ans.<span class="hljs-built_in">begin</span>(), ans.<span class="hljs-built_in">end</span>());<br> <span class="hljs-keyword">while</span> (ans.<span class="hljs-built_in">size</span>() > <span class="hljs-number">1</span> && ans.<span class="hljs-built_in">back</span>() == <span class="hljs-number">0</span>) {<br> ans.<span class="hljs-built_in">pop_back</span>();<br> }<br> <span class="hljs-keyword">return</span> ans;<br>}<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>算法</tag>
</tags>
</entry>
<entry>
<title>使用clink优化cmd</title>
<link href="/p/82bd449c.html"/>
<url>/p/82bd449c.html</url>
<content type="html"><![CDATA[<h2 id="参考链接"><a class="markdownIt-Anchor" href="#参考链接"></a> 参考链接</h2><ul><li><a href="https://github.com/chrisant996/clink">chrisant996/clink: Bash’s powerful command line editing in cmd.exe (github.com)</a></li><li><a href="https://github.com/chrisant996/clink-flex-prompt">chrisant996/clink-flex-prompt: Flex prompt for Clink (github.com)</a></li></ul><h2 id="预先准备"><a class="markdownIt-Anchor" href="#预先准备"></a> 预先准备</h2><p>苦于<code>powershell</code>每次都要一秒多的启动速度,偶然这两天发现了<code>clink</code>这个用于提升<code>cmd</code>体验的工具,尝试了下发现经过简单的配置以后可以替代平时绝大部分<code>ps</code>的需求。因此在这里记录一下基本配置流程。</p><h3 id="安装clink和clink-flex-prompt"><a class="markdownIt-Anchor" href="#安装clink和clink-flex-prompt"></a> 安装clink和clink-flex-prompt</h3><p><code>clink</code>和<code>clink-flex-prompt</code>都可以使用<code>scoop</code>在Windows中完成安装。其中<code>clink</code>的大致效果就是在启动<code>cmd</code>的时候进行注入,来实现一些额外功能的扩展。<code>clink-flex-prompt</code>提供的功能则是类似<code>oh-my-zsh</code>的<code>prompt</code>自定义,让交互界面不至于太苍白</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">scoop install clink clink-flex-prompt<br></code></pre></td></tr></table></figure><p>这两个组件都是在<code>scoop</code>默认的<code>main</code>仓库就有,不需要添加额外的<code>bucket</code>就能直接安装。</p><p>在使用<code>scoop</code>安装了<code>clink</code>之后,还需要使用</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">clink autorun install<br></code></pre></td></tr></table></figure><p>指令来实现每次启动<code>cmd</code>之前都自动启用<code>clink</code>,也可以使用<code>clink inject</code>来在当前的<code>cmd</code>当中暂时体验一下<code>clink</code>的效果</p><h2 id="配置clink"><a class="markdownIt-Anchor" href="#配置clink"></a> 配置clink</h2><p>依据<a href="https://chrisant996.github.io/clink/clink.html#gettingstarted_enhanceddefaults">Clink官方文档</a>中的提及,原本是会有一个<code>Use enhanced default settings</code>的选项来默认实现一些自动填充或快捷键的功能。但在使用<code>scoop</code>安装<code>clink</code>的情况下,至少可以发现自动显示<code>suggestions</code>补全的功能是没有被配置好的。</p><h4 id="自动补全"><a class="markdownIt-Anchor" href="#自动补全"></a> 自动补全</h4><p>对于<code>autosuggest</code>的功能,只需要执行下面的指令就可以实现</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">clink <span class="hljs-built_in">set</span> autosuggest.enable true<br></code></pre></td></tr></table></figure><h4 id="创建inputrc文件"><a class="markdownIt-Anchor" href="#创建inputrc文件"></a> 创建<code>.inputrc</code>文件</h4><p>类似<code>zsh</code>有一个<code>.zshrc</code>,对于<code>clink</code>来说也有一个<code>.inputrc</code>的文件用于初始化<code>clink</code>的配置文件。</p><p>使用<code>CMD</code>输入下面的指令来在<code>Windows</code>的用户目录创建<code>inputrc</code></p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd">notepad <span class="hljs-variable">%userprofile%</span>\.inputrc<br></code></pre></td></tr></table></figure><p>创建好了以后可以在其中粘贴以下内容来实现一些基本的功能:</p><figure class="highlight cmd"><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><code class="hljs cmd"># Some common Readline config settings.<br><br><span class="hljs-built_in">set</span> colored-stats on # Turn on completion colors.<br><span class="hljs-built_in">set</span> colored-completion-prefix on # <span class="hljs-built_in">Color</span> the typed completion prefix.<br><br># Some config settings that only work <span class="hljs-keyword">in</span> Clink.<br><br>$<span class="hljs-keyword">if</span> clink<br><span class="hljs-built_in">set</span> search-ignore-case on # Case insensitive history searches.<br><span class="hljs-built_in">set</span> completion-auto-query-items on # Prompt before showing completions <span class="hljs-keyword">if</span> they'll exceed half the screen.<br>$endif<br><br># Add your keybindings here...<br></code></pre></td></tr></table></figure><blockquote><p>对于具体<code>inputrc</code>的配置写法可以看这里:<a href="https://chrisant996.github.io/clink/clink.html#init-file">Init File</a></p></blockquote><h3 id="使用clink-flex-prompt自定义终端提示"><a class="markdownIt-Anchor" href="#使用clink-flex-prompt自定义终端提示"></a> 使用clink-flex-prompt自定义终端提示</h3><p>使用<code>scoop</code>安装了<code>flex-prompt</code>以后的使用方式也很简单。只需要在<code>cmd</code>里面执行<code>flexprompt configure</code>以后,就可以像<code>p10k</code>那样来自定义一个自己需要的,较为美观的终端提示了。这一块都是有可视化交互的,就不做过多的赘述。</p><h3 id="实现linux中的一些基础指令"><a class="markdownIt-Anchor" href="#实现linux中的一些基础指令"></a> 实现Linux中的一些基础指令</h3><p>平时写代码的时候因为也主要是用<code>Linux</code>系统,对<code>ls</code>和<code>rm</code>这种指令已经敲出肌肉记忆了。<code>Powershell</code>里面对这些指令做了兼容,因此用的时候没有什么额外的感觉,但是切换到<code>cmd</code>的时候就会发现这些指令都不一样了,<code>dir</code>和<code>del</code>一类的指令用的很不顺手。这个时候就有两种解决方法。一种方法算是曲线救国,通过调用<code>git-bash</code>里面提供的工具来实现类似原生<code>Linux</code>的指令,这也是比较推荐的一种方式。</p><h4 id="配置git可执行文件的目录"><a class="markdownIt-Anchor" href="#配置git可执行文件的目录"></a> 配置Git可执行文件的目录</h4><p>在<code>Windows</code>中安装好了<code>Git</code>之后,往往都会有一个<code>git-bash</code>,而我们则可以通过<code>git-bash</code>来实现一些本来在<code>Linux</code>才能执行的指令。而之所以能达到这种效果,是因为<code>Git</code>在安装好了以后在安装目录下有一个<code>/usr/bin</code>的文件夹,里面已经预先放好了可以使用的类似<code>Linux</code>中的一些基本指令,诸如<code>ls</code>和<code>cat</code>这种常用工具都已经有了。因此我们只需要在系统的环境变量中,将<code><Git的安装路径>\usr\bin</code>添加到<code>PATH</code>中即可。</p><p>比如我是使用<code>scoop</code>安装了<code>git</code>,因此我的<code>git</code>安装路径如下:</p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cmd"><span class="hljs-function">C:\<span class="hljs-title">Users</span>\<span class="hljs-title">Halc</span>\<span class="hljs-title">scoop</span>\<span class="hljs-title">apps</span>\<span class="hljs-title">git</span>\<span class="hljs-title">current</span>\<span class="hljs-title">usr</span>\<span class="hljs-title">bin</span></span><br></code></pre></td></tr></table></figure><p>然后我就只需要在环境变量中把这个路径添加到<code>PATH</code>中,我就可以使用我需要的基础工具了:</p><img src="https://lsky.halc.top/UO7PvD.png" alt="修改环境变量" style="zoom:50%;" /><h4 id="使用cmd脚本预先配置好alias"><a class="markdownIt-Anchor" href="#使用cmd脚本预先配置好alias"></a> 使用<code>cmd</code>脚本预先配置好alias</h4><p>还有一种方式是创建<code>alias</code>,以替代原生<code>cmd</code>的一些指令。这种方法是我最先使用的方法,后面发现了<code>git-bash</code>中的工具是需要额外配置环境变量以后就没有使用这种方法了。</p><p>在<code>Windows</code>上创建<code>alias</code>的方法是使用<code>doskey</code>来执行创建,<code>clink</code>则支持在启动的时候自动执行一个<code>cmd</code>脚本,来实现<code>doskey</code>的读入。为此<code>clink</code>默认会从以下路径来寻找<code>clink_start.cmd</code>文件,用以初始化<code>cmd</code>控制台</p><ul><li>Windows XP: <code>C:\Documents and Settings\<username>\Local Settings\Application Data\clink</code></li><li>Windows Vista以及更高的版本:<code>C:\Users\<username>\AppData\Local\clink</code></li></ul><blockquote><p>如果需要修改clink_start.cmd的位置,可以参考这部分文档:<a href="https://chrisant996.github.io/clink/clink.html#filelocations">File Locations</a></p></blockquote><p>在这里我的操作系统目前是<code>Windows 10</code>,因此我只需要在<code>C:\Users\<username>\AppData\Local\clink</code>目录下创建<code>clink_start.cmd</code>文件,并写入以下内容</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><code class="hljs batch">@echo off<br>doskey code=code-insiders $1<br>doskey rm=del<br>doskey cp=copy<br>doskey mv=move<br>doskey of=explorer.exe .<br></code></pre></td></tr></table></figure><p>然后保存以后,就会在下一次启动cmd之前执行这些<code>doskey</code>指令,来实现<code>alias</code>的效果了。</p><h3 id="使用cmd作为windows-terminal的默认应用"><a class="markdownIt-Anchor" href="#使用cmd作为windows-terminal的默认应用"></a> 使用cmd作为Windows Terminal的默认应用</h3><p>最后的最后,只需要在<code>Windows Terminal</code>里面设置<code>cmd</code>为默认的应用,就可以实现每次启动<code>wt</code>的时候,都是秒开<code>cmd</code>的效果了,再也不用每次都等<code>powershell</code>启动才能输入指令了。</p><img src="https://lsky.halc.top/E7bAo0.png" alt="修改Windows Terminal默认配置文件" style="zoom: 50%;" />]]></content>
<categories>
<category>安装引导</category>
</categories>
<tags>
<tag>Windows</tag>
<tag>cmd</tag>
</tags>
</entry>
<entry>
<title>CS144-Lab6 计算机网络:路由转发</title>
<link href="/p/195b5fa9.html"/>
<url>/p/195b5fa9.html</url>
<content type="html"><![CDATA[<h2 id="路由转发"><a class="markdownIt-Anchor" href="#路由转发"></a> 路由转发</h2><p>这个实验就是最后一个需要写代码的实验了。主要需要解决的问题是一个IP数据包传入之后,如何通过已有的路由表确定下一跳的IP地址</p><p>这个实验中主要的问题点就是CIDR的匹配。解决这个问题,我们只需要先将CIDR转为子网掩码,也就是以0为基数,左移(32 - 前缀)位,最后得到的也是子网掩码的值。但是这里有一个问题就是,当mask是uint32_t的时候,如果前缀的长度为0,那么子网掩码会变成255.255.255.255,但是实际上应该是0,因此我们需要对前缀是否为0进行判断。</p><p>在判断完毕之后,只需要将网关的IP地址和子网掩码按位与运算,然后将目标IP地址也和子网掩码按位与运算,如果最后的结果相同,那么就说明子网匹配</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">auto</span> path = _route_table.<span class="hljs-built_in">end</span>();<br><span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> &dst_ip = dgram.<span class="hljs-built_in">header</span>().dst;<br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> entry = _route_table.<span class="hljs-built_in">begin</span>(); entry != _route_table.<span class="hljs-built_in">end</span>(); entry++) {<br> <span class="hljs-comment">// CIDR的子网位数是多少,相当于就是在0的基础上补多少个1,但是当prefix_length == 0的时候,</span><br> <span class="hljs-comment">// 由于位运算的特性,子网掩码会全部变成1,也就相当于是/32的情况。因此当检测到子网掩码是0的时候要直接跳过</span><br> <span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> &mask = entry->prefix_length ? (~<span class="hljs-number">0U</span>) << (<span class="hljs-number">32</span> - entry->prefix_length) : <span class="hljs-number">0</span>;<br> <span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> network_address = entry->route_prefix & mask;<br> <span class="hljs-keyword">if</span> ((dst_ip & mask) == network_address) {<br> path = entry;<br> }<br>}<br></code></pre></td></tr></table></figure><p>如果没有匹配到对应的路由规则,或者这个数据包已经经过了太多次转发都没有找到目的地,那么就将这个数据包丢弃掉。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 检查是否存在对应的路由规则,或者TTL可否生存,如果不符合则丢弃</span><br><span class="hljs-keyword">if</span> (path == _route_table.<span class="hljs-built_in">end</span>() || dgram.<span class="hljs-built_in">header</span>().ttl-- <= <span class="hljs-number">1</span>) {<br> <span class="hljs-keyword">return</span>;<br>}<br></code></pre></td></tr></table></figure><p>如果没有丢弃的话,那么就只需要按照正确的接口将数据包发送出去就行,如果没有下一跳的IP地址,说明数据包已经到达了对应的目的地,只需要直接发送给目标IP即可</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 将数据包发送给正确的接口</span><br>AsyncNetworkInterface &interface = _interfaces[path->interface_num];<br><span class="hljs-keyword">if</span> (path->next_hop.<span class="hljs-built_in">has_value</span>()) {<br> interface.<span class="hljs-built_in">send_datagram</span>(dgram, path->next_hop.<span class="hljs-built_in">value</span>());<br>} <span class="hljs-keyword">else</span> {<br> interface.<span class="hljs-built_in">send_datagram</span>(dgram, Address::<span class="hljs-built_in">from_ipv4_numeric</span>(dgram.<span class="hljs-built_in">header</span>().dst));<br>}<br></code></pre></td></tr></table></figure><blockquote><p>终于也是写完了Lab0-6的所有博客总结😭,Lab7的部分不需要写代码,只需要直接运行程序聊天就行,就不写博客总结了</p></blockquote>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>cs144</tag>
<tag>network</tag>
</tags>
</entry>
<entry>
<title>CS144-Lab5 计算机网络:Network Interface的功能</title>
<link href="/p/db490294.html"/>
<url>/p/db490294.html</url>
<content type="html"><![CDATA[<h2 id="network-interface"><a class="markdownIt-Anchor" href="#network-interface"></a> Network Interface</h2><p>在通过TCP协议将数据包进行封装准备好以后,就需要“快递公司”来对这些数据包进行分发了。这个过程可以划分为两个部分,一个是数据包在中转转发的过程中需要经过的“中转”设备有哪些,其次就是如何选择“中转”的线路。</p><p>在网络接口的部分,主要实现的逻辑是作为发送的某一个节点,在知道了下一个中转站的IP地址以后,如何将数据包进行交付。</p><h3 id="需要实现的逻辑"><a class="markdownIt-Anchor" href="#需要实现的逻辑"></a> 需要实现的逻辑</h3><p>首先对目前的知识进行一个梳理。首先在前面四个Lab里面,主要完成的是TCP数据包从一串简单的字符串,到最后封装成一个完整的,可以用于建立连接沟通的TCP数据包。TCP数据包本身并不关心数据包是如何从源IP到目标IP的,这一部分的主要实现是由网络层的IP路由和数据链路层进行沟通。</p><p>在数据链路层中,我们假设已经通过网络层的路由知道了下一条的IP地址,但是要知道一个网口今天可以是<code>192.168.1.1</code>,明天就可以是<code>10.0.0.1</code>,因此我们只知道IP地址是不足以让我们从硬件层面将数据包进行中转发送的,我们还需要针对每一个特定网口本身的硬件地址,也就是MAC地址,来硬件和硬件之间可以正确的发送数据。</p><p>由于硬件地址和IP地址的映射关系有可能是动态的,而每次发送数据都向所有设备广播询问一次MAC和IP的映射关系的话在交流频繁的网络情况下资源利用率十分低下,因此我们也需要在中转设备中动态维护一个缓存用的映射表,同时为这个映射表中每一个条目设定对应的<code>TTL</code>来保证数据的实时性,在超过一定时间以后就删除该缓存。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//! 构造Arp条目</span><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">ArpEntry</span> {<br> <span class="hljs-type">uint32_t</span> raw_ip_addr;<br> EthernetAddress eth_addr;<br> <span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span><(<span class="hljs-type">const</span> ArpEntry &rhs) <span class="hljs-type">const</span> { <span class="hljs-keyword">return</span> raw_ip_addr < rhs.raw_ip_addr; }<br>};<br><br><span class="hljs-comment">//! 记录最大的ttl时间</span><br><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> arp_max_ttl = <span class="hljs-number">30000</span>;<br><br><span class="hljs-comment">//! 用于记录arp表</span><br>std::map<ArpEntry, <span class="hljs-type">size_t</span>> _arp_table{};<br></code></pre></td></tr></table></figure><p>而获取IP和MAC地址对应关系的这个步骤则是由ARP协议实现,在硬件自己不知道要发送的下一个网口的MAC地址的时候,他就会给所有的网口广播ARP,正确的设备识别到了这个ARP是发送给自己的以后就返回自己的MAC地址,如果不是发送给自己的则丢弃不处理。同时和TCP中的超时重传一样,ARP探针自己也有可能会因为硬件链路的问题而导致对方没有收到自己的报文,所以也需要有一个超时重传的逻辑,来让自己尽可能的收到对方的回复。</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><code class="hljs cpp"><span class="hljs-comment">//! 构造Arp条目</span><br><span class="hljs-keyword">struct</span> <span class="hljs-title class_">ArpEntry</span> {<br> <span class="hljs-type">uint32_t</span> raw_ip_addr;<br> EthernetAddress eth_addr;<br> <span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span><(<span class="hljs-type">const</span> ArpEntry &rhs) <span class="hljs-type">const</span> { <span class="hljs-keyword">return</span> raw_ip_addr < rhs.raw_ip_addr; }<br>};<br><br><span class="hljs-comment">//! 记录最大的探针时间</span><br><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> arp_probe_ttl = <span class="hljs-number">5000</span>;<br><br><span class="hljs-comment">//! 用于记录探针表</span><br>std::map<ArpProbe, <span class="hljs-type">size_t</span>> _probe_table{};<br></code></pre></td></tr></table></figure><p>在有了以上两个大体部分以后,我们就只需要实现</p><ul><li>发送(IPV4/ARP)报文</li><li>接受(IPV4/ARP)报文</li><li>超时重传探针,以及管理ARP映射的TTL</li></ul><p>两个部分即可。</p><h3 id="实现细节"><a class="markdownIt-Anchor" href="#实现细节"></a> 实现细节</h3><h4 id="发送报文"><a class="markdownIt-Anchor" href="#发送报文"></a> 发送报文</h4><p>在发送报文之前,我们首先需要将IP地址转换为uint_32,以用于报文的封装,然后检查这个IP地址我们是否已经缓存了它对应的MAC地址</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// convert IP address of next hop to raw 32-bit representation (used in ARP header)</span><br><span class="hljs-type">const</span> <span class="hljs-type">uint32_t</span> next_hop_ip = next_hop.<span class="hljs-built_in">ipv4_numeric</span>();<br>optional<EthernetAddress> next_eth;<br><br><span class="hljs-comment">// 检查next_hop的IP地址是否在ARP里面有</span><br><span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span> &entry : _arp_table) {<br> <span class="hljs-keyword">if</span> (entry.first.raw_ip_addr == next_hop_ip) {<br> next_eth = entry.first.eth_addr;<br> <span class="hljs-keyword">break</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>如果这个IP地址对应的MAC地址我们已经缓存了,那么就只需要将这个IP报文封装成网络帧进行发送</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 如果在ARP里面有则直接发送并短路</span><br><span class="hljs-keyword">if</span> (next_eth.<span class="hljs-built_in">has_value</span>()) {<br> EthernetFrame eth_frame;<br> eth_frame.<span class="hljs-built_in">header</span>() = {next_eth.<span class="hljs-built_in">value</span>(), _ethernet_address, EthernetHeader::TYPE_IPv4};<br> eth_frame.<span class="hljs-built_in">payload</span>() = dgram.<span class="hljs-built_in">serialize</span>();<br> _frames_out.<span class="hljs-built_in">push</span>(eth_frame);<br> <span class="hljs-keyword">return</span>;<br>}<br></code></pre></td></tr></table></figure><p>如果这个IP地址在我们维护的ARP映射表中并不存在对应的映射关系,那么我们首先要判断我们是否就这个IP发送过ARP探针,如果发送过探针了那么我们也没必要再发送一次,只要等待之前的探针让对方返回正确的MAC地址给我们即可。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// ARP内没有,先判断之前是否已经发送过探针,如果发送过就不发送了</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> &probe : _probe_table) {<br> <span class="hljs-keyword">if</span> (probe.first.raw_ip_addr == next_hop_ip) {<br> <span class="hljs-keyword">return</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>如果没有发送的话,那么我们就需要封装一个ARP探针,用于检测目标IP对应的MAC地址,探针目标的IP地址就是IP数据包下一跳的IP,MAC地址则是广播地址(在该实验中直接将目标MAC设置为空即可)。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 如果没发送就发送,并且将这个探针加入探针表</span><br>ARPMessage arp_probe;<br>arp_probe.opcode = ARPMessage::OPCODE_REQUEST;<br>arp_probe.sender_ethernet_address = _ethernet_address;<br>arp_probe.sender_ip_address = _ip_address.<span class="hljs-built_in">ipv4_numeric</span>();<br>arp_probe.target_ethernet_address = {};<br>arp_probe.target_ip_address = next_hop_ip;<br>EthernetFrame probe_frame;<br>probe_frame.<span class="hljs-built_in">header</span>() = {ETHERNET_BROADCAST, _ethernet_address, EthernetHeader::TYPE_ARP};<br>probe_frame.<span class="hljs-built_in">payload</span>() = arp_probe.<span class="hljs-built_in">serialize</span>();<br>_frames_out.<span class="hljs-built_in">push</span>(probe_frame);<br></code></pre></td></tr></table></figure><p>同时由于探针有超时重传的机制,因此对于这个新发送的报文,我们也需要将其加入缓存表中并设定TTL</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 加入缓存表</span><br>ArpProbe _arp = {next_hop_ip, dgram};<br>_probe_table[_arp] = arp_probe_ttl;<br></code></pre></td></tr></table></figure><p>所有的代码</p><h4 id="接受报文"><a class="markdownIt-Anchor" href="#接受报文"></a> 接受报文</h4><p>在接受报文的部分,我们无非会收到两种报文,一种是包含IP数据的报文,一种是对方给我们发过来的ARP报文</p><p>对于这两种报文,首先我们判断它是不是要发送给我们的或是否是一个广播的网络帧</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 丢弃目标MAC地址不是我自己的数据帧</span><br><span class="hljs-keyword">if</span> (frame.<span class="hljs-built_in">header</span>().dst != _ethernet_address && frame.<span class="hljs-built_in">header</span>().dst != ETHERNET_BROADCAST) {<br> <span class="hljs-keyword">return</span> {};<br>}<br></code></pre></td></tr></table></figure><p>如果这是一个确定源MAC和目标MAC的IP数据包,那么我们只需要接受这个数据包然后返回对应的数据即可</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 接受到IP数据段的时候(代表对方和自己都有了互相的ARP信息,不需要对ARP表进行操作),对这个数据段进行处理</span><br><span class="hljs-keyword">if</span> (frame.<span class="hljs-built_in">header</span>().type == EthernetHeader::TYPE_IPv4) {<br> InternetDatagram datagram;<br> datagram.<span class="hljs-built_in">parse</span>(frame.<span class="hljs-built_in">payload</span>());<br> <span class="hljs-keyword">return</span> datagram;<br>}<br></code></pre></td></tr></table></figure><p>但是如果这是一个ARP探针,我们首先对其进行分析</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 接受到的是一个ARP包,先将这个包的内容序列化,并将其中包含的ARP信息尝试更新到自己的ARP表中</span><br>ARPMessage arp_msg;<br>arp_msg.<span class="hljs-built_in">parse</span>(frame.<span class="hljs-built_in">payload</span>());<br>ArpEntry src = {arp_msg.sender_ip_address, arp_msg.sender_ethernet_address},<br> dst = {arp_msg.target_ip_address, arp_msg.target_ethernet_address};<br><br>_update_arp_table({src, dst});<br></code></pre></td></tr></table></figure><blockquote><p>其中_update_arp_table用于更新arp表,具体代码如下</p></blockquote><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//! 更新ARP条目</span><br><span class="hljs-type">void</span> NetworkInterface::_update_arp_table(initializer_list<ArpEntry> arp_entry) {<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> &entry : arp_entry) {<br> _arp_table[entry] = arp_max_ttl;<br> }<br>}<br></code></pre></td></tr></table></figure><p>在解析了网络帧之后,我们大致可以得到以下三种分类</p><h5 id="广播报文请求某个ip对应的mac地址但这个ip不是我们的"><a class="markdownIt-Anchor" href="#广播报文请求某个ip对应的mac地址但这个ip不是我们的"></a> 广播报文,请求某个IP对应的MAC地址,但这个IP不是我们的</h5><p>丢弃过滤</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 过滤掉不是发给自己的IP地址的包</span><br><span class="hljs-keyword">if</span> (dst.raw_ip_addr != _ip_address.<span class="hljs-built_in">ipv4_numeric</span>()) {<br> <span class="hljs-keyword">return</span> {};<br>}<br></code></pre></td></tr></table></figure><h5 id="广播报文请求某个ip对应的mac地址但这个ip是我们的"><a class="markdownIt-Anchor" href="#广播报文请求某个ip对应的mac地址但这个ip是我们的"></a> 广播报文,请求某个IP对应的MAC地址,但这个IP是我们的</h5><p>返回我们的MAC地址</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 接受到ARP请求的时候,返回一个包含自己信息的ARP响应报文,同时利用这个frame更新自己的ARP表</span><br><span class="hljs-keyword">if</span> (arp_msg.opcode == ARPMessage::OPCODE_REQUEST) {<br> ARPMessage reply;<br> reply.opcode = ARPMessage::OPCODE_REPLY;<br> reply.sender_ip_address = _ip_address.<span class="hljs-built_in">ipv4_numeric</span>();<br> reply.sender_ethernet_address = _ethernet_address;<br> reply.target_ip_address = src.raw_ip_addr;<br> reply.target_ethernet_address = src.eth_addr;<br><br> EthernetFrame reply_frame;<br> reply_frame.<span class="hljs-built_in">header</span>() = {src.eth_addr, _ethernet_address, EthernetHeader::TYPE_ARP};<br> reply_frame.<span class="hljs-built_in">payload</span>() = reply.<span class="hljs-built_in">serialize</span>();<br> _frames_out.<span class="hljs-built_in">push</span>(reply_frame);<br> <span class="hljs-keyword">return</span> {};<br>}<br></code></pre></td></tr></table></figure><h5 id="我们发出的arp探针得到了别人的回复知道了别人的mac地址"><a class="markdownIt-Anchor" href="#我们发出的arp探针得到了别人的回复知道了别人的mac地址"></a> 我们发出的ARP探针得到了别人的回复,知道了别人的MAC地址</h5><p>更新自己的ARP映射表,同时检查是否有对应目标MAC地址的报文等待发送</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 收到别人传送回来的ARP的时候,如果缓存中有等待的对应条目,则删除,并发送对应的数据</span><br><span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> entry = _probe_table.<span class="hljs-built_in">begin</span>(); entry != _probe_table.<span class="hljs-built_in">end</span>();) {<br> <span class="hljs-keyword">if</span> (entry->first.raw_ip_addr == src.raw_ip_addr) {<br> <span class="hljs-built_in">send_datagram</span>(entry->first.datagram, Address::<span class="hljs-built_in">from_ipv4_numeric</span>(entry->first.raw_ip_addr));<br> entry = _probe_table.<span class="hljs-built_in">erase</span>(entry);<br> } <span class="hljs-keyword">else</span> {<br> entry++;<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="超时处理"><a class="markdownIt-Anchor" href="#超时处理"></a> 超时处理</h4><p>在这里我们只需要做两件事</p><ul><li>删除超时的ARP条目</li><li>重新发送超时的探针</li></ul><p>对于正常的条目和探针,我们只需要让其TTL减少即可,代码如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">NetworkInterface::tick</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">size_t</span> ms_since_last_tick)</span> </span>{<br> <span class="hljs-comment">// 将检测是否有超时的ARP条目</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> entry = _arp_table.<span class="hljs-built_in">begin</span>(); entry != _arp_table.<span class="hljs-built_in">end</span>();) {<br> <span class="hljs-keyword">if</span> (entry->second < ms_since_last_tick) {<br> <span class="hljs-comment">// 删除多余的ARP条目</span><br> entry = _arp_table.<span class="hljs-built_in">erase</span>(entry);<br> } <span class="hljs-keyword">else</span> {<br> entry->second -= ms_since_last_tick;<br> entry++;<br> }<br> }<br> <span class="hljs-comment">// 检测是否有超时的探针</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> entry = _probe_table.<span class="hljs-built_in">begin</span>(); entry != _probe_table.<span class="hljs-built_in">end</span>(); entry++) {<br> <span class="hljs-keyword">if</span> (entry->second < ms_since_last_tick) {<br> <span class="hljs-comment">// 重新发送超时的探针</span><br> ARPMessage re_probe_arp;<br> re_probe_arp.opcode = ARPMessage::OPCODE_REQUEST;<br> re_probe_arp.sender_ip_address = _ip_address.<span class="hljs-built_in">ipv4_numeric</span>();<br> re_probe_arp.sender_ethernet_address = _ethernet_address;<br> re_probe_arp.target_ip_address = entry->first.raw_ip_addr;<br> re_probe_arp.target_ethernet_address = {};<br><br> EthernetFrame re_probe_frame;<br> re_probe_frame.<span class="hljs-built_in">header</span>() = {ETHERNET_BROADCAST, _ethernet_address, EthernetHeader::TYPE_ARP};<br> re_probe_frame.<span class="hljs-built_in">payload</span>() = re_probe_arp.<span class="hljs-built_in">serialize</span>();<br> _frames_out.<span class="hljs-built_in">push</span>(re_probe_frame);<br><br> <span class="hljs-comment">// 重置探针条目对应的时间</span><br> entry->second = arp_probe_ttl;<br> } <span class="hljs-keyword">else</span> {<br> entry->second -= ms_since_last_tick;<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h4><p>这个实验主要实现的逻辑都是数据链路层的,和之前几个Lab没有直接的关系。不过值得一提的就是在Lab4测试的时候运行的<code>TUN</code>和<code>TAP</code>很有意思。这两个词之前好奇还是在使用<code>Clash</code>的时候既可以是<code>TUN</code>也可以是<code>TAP</code>模式,而且通常来说<code>TUN</code>模式的性能要比<code>TAP</code>要好。当时还不知道为什么,在写完这个实验以后搜了一些资料,目前浅显的理解大致认为是<code>TAP</code>的网络代理模拟是有处理到数据链路层的,也就是MAC地址也进行了模拟,而<code>TUN</code>则只是模拟到了IP层,并没有自己的MAC地址,因此损耗也要少一些。</p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>cs144</tag>
<tag>network</tag>
</tags>
</entry>
<entry>
<title>CS144-Lab4 计算机网络:TCP Connection的实现</title>
<link href="/p/10e77bc5.html"/>
<url>/p/10e77bc5.html</url>
<content type="html"><![CDATA[<h2 id="tcp-connection"><a class="markdownIt-Anchor" href="#tcp-connection"></a> TCP Connection</h2><p>TCP Connection的部分本身并不难,这个实验的主要核心是学习使用<code>tshark</code>或<code>wireshark</code>一类的工具对TCP的网络状况进行分析,找出正确或错误的数据包。</p><h3 id="需要实现的逻辑"><a class="markdownIt-Anchor" href="#需要实现的逻辑"></a> 需要实现的逻辑</h3><p>在这个实验中我们需要将前面写的<code>TCP Sender</code>和<code>TCP Receiver</code>两个部分的逻辑进行合并,使得两者之间可以进行数据的传输。</p><p>除了几个可以直接调用前面实验函数的函数以外,我们主要需要完成的我认为是收到某个报文以后的处理函数<code>segment_received(const TCPSegment &seg)</code>和时间函数<code>tick()</code>。</p><h3 id="实现细节"><a class="markdownIt-Anchor" href="#实现细节"></a> 实现细节</h3><h4 id="接受报文"><a class="markdownIt-Anchor" href="#接受报文"></a> 接受报文</h4><p>对于接受报文这个函数,首先通过对实验报告的分析,我们可以知道我们主要要做的事情可以分为以下三个大逻辑:</p><h5 id="对报文本身合法性的分析"><a class="markdownIt-Anchor" href="#对报文本身合法性的分析"></a> 对报文本身合法性的分析</h5><ul><li>记录收到这个报文的时间,无论对错</li><li>检查这个报文是否是带RST标志的报文,如果是的话则直接断开连接</li><li>如果是在LISTEN状态的时候接受到这个报文的,则要判断对方是否连接</li></ul><p>代码如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 首先无论如何,刷新收到报文的时间</span><br>_time_since_last_segment_received = <span class="hljs-number">0</span>;<br><br><span class="hljs-comment">// 然后先检查这个报文是否出错,如果出错则直接返回</span><br><span class="hljs-keyword">if</span> (seg.<span class="hljs-built_in">header</span>().rst) {<br> _receiver.<span class="hljs-built_in">stream_out</span>().<span class="hljs-built_in">set_error</span>();<br> _sender.<span class="hljs-built_in">stream_in</span>().<span class="hljs-built_in">set_error</span>();<br> _is_active = <span class="hljs-literal">false</span>;<br> <span class="hljs-keyword">return</span>;<br>}<br><br><span class="hljs-comment">// 如果TCP连接处于LISTEN状态,只接受SYN报文,并且返回一个SYN + ACK的报文</span><br><span class="hljs-keyword">if</span> (<span class="hljs-keyword">not</span> _receiver.<span class="hljs-built_in">ackno</span>().<span class="hljs-built_in">has_value</span>()) {<br> <span class="hljs-keyword">if</span> (seg.<span class="hljs-built_in">header</span>().syn) {<br> _sender.<span class="hljs-built_in">fill_window</span>();<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><h5 id="对报文进行处理"><a class="markdownIt-Anchor" href="#对报文进行处理"></a> 对报文进行处理</h5><p>接受这个报文,如果带有ACK信息则更新对方已经确认了的<code>ackno</code>和对方当前的<code>window_size</code></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 接受这个报文</span><br>_receiver.<span class="hljs-built_in">segment_received</span>(seg);<br><br><span class="hljs-comment">// 对ACK报文进行确认更新,用于下一次更新的确认</span><br><span class="hljs-keyword">if</span> (seg.<span class="hljs-built_in">header</span>().ack) {<br> _sender.<span class="hljs-built_in">ack_received</span>(seg.<span class="hljs-built_in">header</span>().ackno, seg.<span class="hljs-built_in">header</span>().win);<br>}<br></code></pre></td></tr></table></figure><h5 id="正确的处理连接的关闭"><a class="markdownIt-Anchor" href="#正确的处理连接的关闭"></a> 正确的处理连接的关闭</h5><p>这部分是我认为<code>Lab4</code>里面在理解上较难的部分。其中,TCP的断开分为三种不同的情况:</p><ol><li>由于RST标志导致的强制退出(unclean shutdown)</li><li>正常的通讯结束而导致的关闭(clean shutdown)</li></ol><p>但是对于第二种情况,我们可以进一步分为两种情况:</p><p>首先是最简单的四次挥手报文:</p><ol><li><p><code>Client</code>发送完毕数据,告诉<code>Server</code>我结束(FIN)了<br /><code>Client</code>客户端在给<code>Server</code>发送完毕了所有数据以后,主动发送<code>FIN</code>数据包,表示自己的数据已经发送完毕了。然后<code>Server</code>在收到<code>Client</code>的<code>FIN</code>报文并处理完毕以后则会返回一个<code>FIN ACK</code>报文,来告诉<code>Client</code>他发过来的数据已经在服务端被处理完成了。</p></li><li><p><code>Server</code>也发送完毕了数据,告诉<code>Client</code>我结束(FIN)了<br />这个时候<code>Server</code>也会给<code>Client</code>发送一个<code>FIN</code>的报文,同样等待<code>Client</code>那边确认,如果<code>Client</code>发送了确认报文来确认这个ACK,则代表客户端那边也处理完了,这个时候按理来说首先提出数据发送完毕的<code>Client</code>就可以断开链接了。</p></li></ol><h6 id="client主动关闭"><a class="markdownIt-Anchor" href="#client主动关闭"></a> Client:主动关闭</h6><p><strong>但是,服务端有可能收不到</strong>这最后一个ACK确认报文,从而导致自己一直在等待客户端向自己发送<code>ACK</code>确认报文。</p><p>为了避免这种情况,最简单的处理方法就是让<code>Client</code>给<code>Server</code>发送了<code>FIN ACK</code>报文以后不要急着断开连接,而是设置一个计时器,等待看看<code>Server</code>会不会重传<code>FIN</code>报文。</p><p>如果重传了<code>FIN</code>则代表<code>Server</code>并没有收到先前发送的<code>FIN ACK</code>,这个时候<code>Client</code>就需要重新发送一个<code>ACK</code>回去,告知<code>Server</code>可以断开连接了。</p><p>如果超过了计时器的时间,<code>Client</code>也没有收到<code>Server</code>的重传报文,那么我们就假设<code>Server</code>已经收到了<code>FIN ACK</code>,并且已经关闭了他那边的连接,这个时候<code>Client</code>就可以断开连接了。而这段计时器的等待时间,就是实验中的<code>linger_time = 10 *_cfg.rt_timeout</code>,这个时间往往是比<code>Server</code>超时重传的时间大很多的,也就留给了<code>Server</code>足够多的时间来重传<code>FIN</code>报文。</p><h6 id="server被动关闭"><a class="markdownIt-Anchor" href="#server被动关闭"></a> Server:被动关闭</h6><p>服务端这边就很简单了,在发送完自己的<code>FIN</code>之后,只需要正常等待<code>Client</code>的<code>ACK</code>确认报文,如果没有等到则重传<code>FIN</code>,如果等到了则直接断开连接。</p><h6 id="连接关闭的代码实现"><a class="markdownIt-Anchor" href="#连接关闭的代码实现"></a> 连接关闭的代码实现</h6><p>知道了上面的区分以后,我们实现起来就很简单了,只需要通过添加一个变量<code>_linger_after_streams_finish</code>来判断到底是对方先结束还是自己先结束。如果是对方先结束,则我们不需要等待<code>linger_time</code>,在后面收到了<code>FIN</code>报文以后直接断开连接即可。否则则需要在后面<code>tick()</code>函数的部分添加超时断开连接的逻辑。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 接收到正确的EOF报文,代表对方发送过来的数据流已经结束了,但是自己还有数据要发送</span><br><span class="hljs-comment">// 因此需要等待自己的数据流发送完毕后才能关闭连接</span><br><span class="hljs-keyword">if</span> (_receiver.<span class="hljs-built_in">stream_out</span>().<span class="hljs-built_in">eof</span>() && <span class="hljs-keyword">not</span> _sender.<span class="hljs-built_in">stream_in</span>().<span class="hljs-built_in">eof</span>()) {<br> _linger_after_streams_finish = <span class="hljs-literal">false</span>;<br>}<br><br><span class="hljs-comment">// _linger_after_streams_finish是false说明对方发送给我们的数据流已经全部被接受了</span><br><span class="hljs-comment">// 此时有_sender的eof和bytes_in_flight都为0,说明自己的数据流也已经全部发送完毕</span><br><span class="hljs-comment">// 因此可以关闭连接了</span><br><span class="hljs-keyword">if</span> (_sender.<span class="hljs-built_in">stream_in</span>().<span class="hljs-built_in">eof</span>() && <span class="hljs-built_in">bytes_in_flight</span>() == <span class="hljs-number">0</span> && <span class="hljs-keyword">not</span> _linger_after_streams_finish) {<br> _is_active = <span class="hljs-literal">false</span>;<br>}<br></code></pre></td></tr></table></figure><h5 id="发送确认报文"><a class="markdownIt-Anchor" href="#发送确认报文"></a> 发送确认报文</h5><p>这部分逻辑就很简单了,如果在接受了对方传来的有序列号消耗数据包以后,我们并没有数据要传输(即无法告知对方我们接受到了数据),那么我们就需要单独传输一个ACK数据包给对方,告知我们已经接收到了对方的数据。(如果对方发送给我们的是一个ACK数据包,我们则不需要回复,也就是收到了占用序号为零的包)</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">if</span> (_sender.<span class="hljs-built_in">segments_out</span>().<span class="hljs-built_in">empty</span>() &&<br> (seg.<span class="hljs-built_in">length_in_sequence_space</span>() || seg.<span class="hljs-built_in">header</span>().seqno != _receiver.<span class="hljs-built_in">ackno</span>())) {<br> _sender.<span class="hljs-built_in">send_empty_segment</span>();<br>}<br></code></pre></td></tr></table></figure><p>其中<code>seg.header().seqno != _receiver.ackno()</code>代表的是一种特殊情况,在<code>TCP</code>连接中,有的时候为了确认当前连接是否依旧有效,对方有可能会随机发送一个错误的序列号给我们,这个时候我们就需要回复一个ACK报文给对方,以此告知对方这个连接依旧是有效的,同时也可以让对方更新我们的窗口大小。</p><h5 id="接受报文的代码实现"><a class="markdownIt-Anchor" href="#接受报文的代码实现"></a> 接受报文的代码实现</h5><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">TCPConnection::segment_received</span><span class="hljs-params">(<span class="hljs-type">const</span> TCPSegment &seg)</span> </span>{<br> <span class="hljs-comment">// 首先无论如何,刷新收到报文的时间</span><br> _time_since_last_segment_received = <span class="hljs-number">0</span>;<br><br> <span class="hljs-comment">// 然后先检查这个报文是否出错,如果出错则直接返回</span><br> <span class="hljs-keyword">if</span> (seg.<span class="hljs-built_in">header</span>().rst) {<br> _receiver.<span class="hljs-built_in">stream_out</span>().<span class="hljs-built_in">set_error</span>();<br> _sender.<span class="hljs-built_in">stream_in</span>().<span class="hljs-built_in">set_error</span>();<br> _is_active = <span class="hljs-literal">false</span>;<br> <span class="hljs-keyword">return</span>;<br> }<br><br> <span class="hljs-comment">// 如果TCP连接处于LISTEN状态,只接受SYN报文,并且返回一个SYN + ACK的报文</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">not</span> _receiver.<span class="hljs-built_in">ackno</span>().<span class="hljs-built_in">has_value</span>()) {<br> <span class="hljs-keyword">if</span> (seg.<span class="hljs-built_in">header</span>().syn) {<br> _sender.<span class="hljs-built_in">fill_window</span>();<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span>;<br> }<br> }<br><br> <span class="hljs-comment">// 接受这个报文</span><br> _receiver.<span class="hljs-built_in">segment_received</span>(seg);<br><br> <span class="hljs-comment">// 对ACK报文进行确认更新,用于下一次更新的确认</span><br> <span class="hljs-keyword">if</span> (seg.<span class="hljs-built_in">header</span>().ack) {<br> _sender.<span class="hljs-built_in">ack_received</span>(seg.<span class="hljs-built_in">header</span>().ackno, seg.<span class="hljs-built_in">header</span>().win);<br> }<br><br> <span class="hljs-comment">// 接收到正确的EOF报文,代表对方发送过来的数据流已经结束了,但是自己还有数据要发送</span><br> <span class="hljs-comment">// 因此需要等待自己的数据流发送完毕后才能关闭连接</span><br> <span class="hljs-keyword">if</span> (_receiver.<span class="hljs-built_in">stream_out</span>().<span class="hljs-built_in">eof</span>() && <span class="hljs-keyword">not</span> _sender.<span class="hljs-built_in">stream_in</span>().<span class="hljs-built_in">eof</span>()) {<br> _linger_after_streams_finish = <span class="hljs-literal">false</span>;<br> }<br><br> <span class="hljs-comment">// _linger_after_streams_finish是false说明对方发送给我们的数据流已经全部被接受了</span><br> <span class="hljs-comment">// 此时有_sender的eof和bytes_in_flight都为0,说明自己的数据流也已经全部发送完毕</span><br> <span class="hljs-comment">// 因此可以关闭连接了</span><br> <span class="hljs-keyword">if</span> (_sender.<span class="hljs-built_in">stream_in</span>().<span class="hljs-built_in">eof</span>() && <span class="hljs-built_in">bytes_in_flight</span>() == <span class="hljs-number">0</span> && <span class="hljs-keyword">not</span> _linger_after_streams_finish) {<br> _is_active = <span class="hljs-literal">false</span>;<br> }<br><br> <span class="hljs-keyword">if</span> (_sender.<span class="hljs-built_in">segments_out</span>().<span class="hljs-built_in">empty</span>() && (seg.<span class="hljs-built_in">length_in_sequence_space</span>() || seg.<span class="hljs-built_in">header</span>().seqno != _receiver.<span class="hljs-built_in">ackno</span>())) {<br> _sender.<span class="hljs-built_in">send_empty_segment</span>();<br> }<br><br> <span class="hljs-comment">// 填装需要发送的报文</span><br> _push_out();<br>}<br></code></pre></td></tr></table></figure><h4 id="时间流动"><a class="markdownIt-Anchor" href="#时间流动"></a> 时间流动</h4><p>另外一个需要注意的函数就是<code>tick()</code>函数了。其实这一部分的重要也主要是连带了前面接受报文部分的关闭连接,主要要注意的就是添加一个对<code>linger_time</code>的判断。整个<code>tick()</code>函数的代码如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//! \param[in] ms_since_last_tick number of milliseconds since the last call to this method</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">TCPConnection::tick</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">size_t</span> ms_since_last_tick)</span> </span>{<br> _time_since_last_segment_received += ms_since_last_tick;<br> _sender.<span class="hljs-built_in">tick</span>(ms_since_last_tick);<br> <span class="hljs-comment">// 如果超时重传次数超过了最大重传次数,那么就直接关闭连接</span><br> <span class="hljs-keyword">if</span> (_sender.<span class="hljs-built_in">consecutive_retransmissions</span>() > TCPConfig::MAX_RETX_ATTEMPTS) {<br> _send_rst();<br> <span class="hljs-keyword">return</span>;<br> }<br> <span class="hljs-comment">// 在我方的数据包全部发送并且处理完毕以后,如果接受到了对方传来的EOF报文,并且等待了十倍的RTT时间都没有新的报文传来,则代表连接已经关闭</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">time_since_last_segment_received</span>() >= _linger_time && _sender.<span class="hljs-built_in">stream_in</span>().<span class="hljs-built_in">eof</span>() &&<br> _receiver.<span class="hljs-built_in">stream_out</span>().<span class="hljs-built_in">input_ended</span>()) {<br> _is_active = <span class="hljs-literal">false</span>;<br> }<br> _push_out();<br>}<br></code></pre></td></tr></table></figure><h4 id="问题和难点"><a class="markdownIt-Anchor" href="#问题和难点"></a> 问题和难点</h4><p><code>Lab4</code>实验主要的难点感觉还是在即使跑通了前面大部分的基本测试,也还是有可能因为<code>Lab2</code>和<code>Lab3</code>里面的疏忽,而导致后面模拟真实通讯的时候很容易难以下手。但是在掌握了<code>Wireshark</code>抓包一类的工具用法以后还是很容易发现问题所在并加以纠正的。</p><p>比如我在<code>Lab3</code>中,对于<code>TCPSender</code>在填充窗口大小的时候,一开始并不是设置了一个额外的变量<code>fill_space</code>来控制可以发送的空闲空间的大小,而是直接使用了<code>ack_received</code>方法中收到的最新窗口大小,忽略了<code>bytes_in_flight()</code>也需要考虑在窗口占用里面的问题。在使用<code>Wireshark</code>抓包的时候就明显发现了发送数据包的序号要远超于接收方的确认序号</p><p><img src="https://lsky.halc.top/cZQdDr.png" alt="空闲窗口判断错误" /></p><p>而这个问题也在我通过修改<code>Lab3</code>对应空闲窗口大小的逻辑之后得到了解决。</p><p>我还遇到过的第二个问题就是在小窗口的情况下,没有正确处理链接的关闭。在通过<code>Wireshark</code>抓包以后可以看到</p><p><img src="https://lsky.halc.top/zvSqG9.png" alt="没有正确处理关闭" /></p><p>在发送方还没有给接收方发送完所有数据的时候,接收方就提前终止了自己的连接,这个问题主要出在<code>tick()</code>函数里面关于<code>linger_time</code>的逻辑错误,我并没有等到接收方接受到EOF就直接关闭了链接。错误代码如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">if</span> (<span class="hljs-built_in">time_since_last_segment_received</span>() >= _linger_time && _sender.<span class="hljs-built_in">stream_in</span>().<span class="hljs-built_in">eof</span>()) {<br> _is_active = <span class="hljs-literal">false</span>;<br>}<br></code></pre></td></tr></table></figure><p>修改后的代码如下:</p><figure class="highlight diff"><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><code class="hljs diff">if (time_since_last_segment_received() >= _linger_time && _sender.stream_in().eof()<br><span class="hljs-addition">+ && _receiver.stream_out().input_ended()) {</span><br> _is_active = false;<br>}<br></code></pre></td></tr></table></figure><p>整个<code>Lab4</code>的代码可以在<code>Github</code>的仓库查看:</p><p><a href="https://github.com/HalcyonAzure/CS144/blob/master/libsponge/tcp_connection.cc">tcp_connection.cc</a><br /><a href="https://github.com/HalcyonAzure/CS144/blob/master/libsponge/tcp_connection.hh">tcp_connection.hh</a></p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>cs144</tag>
<tag>network</tag>
</tags>
</entry>
<entry>
<title>使用Yadm管理Linux配置文件</title>
<link href="/p/4457ea2b.html"/>
<url>/p/4457ea2b.html</url>
<content type="html"><![CDATA[<p>今年暑假看<code>Missing-course</code>的时候开始意识到备份<code>Linux</code>配置文件的重要性,以后即使切换机器也可以很容易的恢复自己喜欢的编程环境。在简单搜索了几个方式以后,决定使用<code>yadm</code>作为管理工具,并且写了一个模板,便于分享和使用。</p><ul><li><a href="https://github.com/HalcyonAzure/Yadm_Dotfiles">Yadm Dotfiles</a></li></ul><h2 id="使用说明"><a class="markdownIt-Anchor" href="#使用说明"></a> 使用说明</h2><p>该仓库的主要用途为使用<code>yadm</code>作为管理工具,通过<code>Github</code>来同步自己的<code>Dotfiles</code></p><ol><li><p>先在自己当前环境下安装<code>yadm</code>,具体安装说明参照<a href="https://yadm.io/docs/install">Installation</a></p></li><li><p>通过<code>Use this template</code>或者下载源码的方式,创建并上传到自己的一个仓库中,最好是<code>Private</code>类型</p></li><li><p>在本身没有经过配置<code>Dotfiles</code>的环境下(或提前备份好自己的<code>Dotfiles</code>)输入以下指令拉取模板到本地进行管理</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">yadm clone https://github.com/<your id>/<your repository> --bootstrap<br></code></pre></td></tr></table></figure></li><li><p>在附带<code>--boostrap</code>的情况下执行完毕上述指令以后将会按默认模板文件使用<code>git</code>来对自己的<code>Dotfiles</code>进行管理</p></li></ol><hr /><h2 id="模板介绍"><a class="markdownIt-Anchor" href="#模板介绍"></a> 模板介绍</h2><h3 id="bootstrap"><a class="markdownIt-Anchor" href="#bootstrap"></a> Bootstrap</h3><p>在<code>clone</code>仓库的时候如果附带了<code>bootstrap</code>将会自动执行以下三件事</p><ul><li>在以 <code>apt/yum/pacman</code> 作为包管理器的情况下安装<code>zsh vim tmux curl wget openssl</code></li><li>安装 <code>oh-my-zsh</code>,并以<code>p10k</code>为主题。安装<code>zsh-autosuggestions</code>,<code>zsh-syntax-highlighting</code>和<code>zsh-proxy</code>三个插件</li><li>切换 <code>zsh</code> 为当前用户的默认终端</li></ul><h3 id="配置文件"><a class="markdownIt-Anchor" href="#配置文件"></a> 配置文件</h3><h4 id="sshconfig"><a class="markdownIt-Anchor" href="#sshconfig"></a> .ssh/config</h4><ul><li>取消了域名指纹检查,不需要输入yes同意首次链接</li></ul><h4 id="zshrc"><a class="markdownIt-Anchor" href="#zshrc"></a> .zshrc</h4><ul><li>配置了<code>rust</code>国内镜像</li><li>添加了<a href="https://transfer.sh/">transfer</a>用于分享文件(输入<code>transfer /path/to/file</code>即可)</li><li>使用<code>pws</code>作为<code>powershell.exe</code>的<code>alias</code>,便于<code>wsl</code>环境下使用<code>powershell</code></li><li>启用了<code>vi</code>的normal模式,在输入命令的时候按<code>Esc</code>即可</li></ul><h4 id="vimrc"><a class="markdownIt-Anchor" href="#vimrc"></a> .vimrc</h4><ul><li>参考<a href="https://missing-semester-cn.github.io/2020/editors/">missing-semster</a></li><li>为了便于作为<code>wsl</code>环境使用,在<code>Windows Terminal</code>下复制粘贴,关闭了鼠标功能</li></ul><h4 id="cargo"><a class="markdownIt-Anchor" href="#cargo"></a> .cargo</h4><ul><li>配置好了<code>cargo</code>镜像库</li></ul><h3 id="gitconfig"><a class="markdownIt-Anchor" href="#gitconfig"></a> gitconfig</h3><ul><li>配置默认使用<code>~/.gitignore作为全局</code>ignore`文件</li><li>在使用<code>https</code>时的验证交由<code>.git-credentials</code>文件纯文本保存密码(有风险,可加密)</li></ul><h3 id="加密使用"><a class="markdownIt-Anchor" href="#加密使用"></a> 加密使用</h3><p>对于类似<code>.ssh/id_rsa</code>或者<code>.git-credentials</code>文件可以通过<code>yadm</code>自带的<code>encrypt</code>工具进行加密,使用步骤如下</p><ol><li><p>在<code>.config/yadm/encrypt</code>文件内写入需要加密的文件路径,支持正则匹配</p></li><li><p>假设在<code>.ssh/</code>目录下所有文件(例如<code>config, id_rsa</code>)都需要进行加密,则在<code>.config/yadm/encrypt</code>写入<code>.ssh/*</code>后输入以下指令(安装openssl为前提)</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">yadm encrypt<br></code></pre></td></tr></table></figure><p>则会要求输入一个密码来进行加密</p></li><li><p>加密完则会在<code>.local/share/yadm</code>目录下产生一个<code>archive</code>作为加密打包后的文件,将该文件添加并上传到<code>Github</code></p><blockquote><p>虽然加密文件本身有一定安全性,但为了保险起见还是推荐使用<code>Private</code>仓库来存储自己的<code>Dotfiles</code></p></blockquote><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><code class="hljs shell">yadm add ~/.local/share/yadm/archive<br>yadm commit -m "add encrypt archive"<br>yadm push<br></code></pre></td></tr></table></figure><p>原本的文件此时将会依旧本地存在于(例如<code>config, id_rsa</code>),但不需要上传到<code>Github</code>当中</p></li><li><p>在下次重装系统/更换环境的时候,如果需要通过<code>yadm</code>对环境进行复原并解密加密文件,则只需要输入以下指令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">yadm decrypt<br></code></pre></td></tr></table></figure><p>就会将加密打包的文件解密到对应的文件目录,保证一定的安全性</p></li></ol>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>Yadm</tag>
</tags>
</entry>
<entry>
<title>CS144-Lab3 计算机网络:TCP Sender的实现</title>
<link href="/p/73e1b791.html"/>
<url>/p/73e1b791.html</url>
<content type="html"><![CDATA[<h2 id="tcp-sender"><a class="markdownIt-Anchor" href="#tcp-sender"></a> TCP Sender</h2><h3 id="需要实现的主要逻辑"><a class="markdownIt-Anchor" href="#需要实现的主要逻辑"></a> 需要实现的主要逻辑</h3><ol><li>追踪<code>Receiver</code>返回的<code>windows_size</code>(可接受的剩余容量)和<code>ackno</code>(已经确认接收的字符位置)</li><li>只要数据来了就直接对数据进行封装并发送,只有在窗口被消耗为零的情况下才停止发送</li><li>将没有被<code>acknowledge</code>的数据包存储起来,在超时的时候进行发送</li></ol><h3 id="实现细节"><a class="markdownIt-Anchor" href="#实现细节"></a> 实现细节</h3><ol><li>对于超时重传的时间判断,使用已经提供的<code>tick()</code>函数,每次调用的时候传入多少时间就消耗了多少时间</li><li>超时重传的默认基准值会以成员变量的形式在<code>TCPSender</code>中进行初始化</li><li>在<code>TCPSegment</code>中有一个<code>_segments_out</code>的成员,只需要向这个<code>queue</code>内<code>push</code>一个<code>TCPSegment</code>就相当于将这个数据段发送了</li></ol><h3 id="代码实现"><a class="markdownIt-Anchor" href="#代码实现"></a> 代码实现</h3><h4 id="额外定义成员"><a class="markdownIt-Anchor" href="#额外定义成员"></a> 额外定义成员</h4><p>对于计时器的部分,为了方便抽象管理,我这里选择直接创建一个类来进行封装</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">TCPTimer</span> {<br> <span class="hljs-keyword">private</span>:<br> <span class="hljs-type">size_t</span> _tick_passed = <span class="hljs-number">0</span>; <span class="hljs-comment">// 记录实时的时间戳</span><br> <span class="hljs-type">size_t</span> _rto_timeout = <span class="hljs-number">0</span>; <span class="hljs-comment">// 记录超过多久时间没有收到ACK就重传</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> _rto_count = <span class="hljs-number">0</span>; <span class="hljs-comment">// 记录重传的次数</span><br><br> <span class="hljs-type">bool</span> _is_running{<span class="hljs-literal">false</span>}; <span class="hljs-comment">// 记录计时器是否启动</span><br><br> <span class="hljs-keyword">public</span>:<br> <span class="hljs-comment">// 重置计时器</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">reset</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">uint16_t</span> retx_timeout)</span> </span>{<br> _rto_count = <span class="hljs-number">0</span>;<br> _rto_timeout = retx_timeout;<br> _tick_passed = <span class="hljs-number">0</span>;<br> }<br><br> <span class="hljs-comment">// 启动计时器</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">run</span><span class="hljs-params">()</span> </span>{ _is_running = <span class="hljs-literal">true</span>; }<br><br> <span class="hljs-comment">// 暂停计时器</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">stop</span><span class="hljs-params">()</span> </span>{ _is_running = <span class="hljs-literal">false</span>; }<br><br> <span class="hljs-comment">// 计时器是否启动</span><br> <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">is_running</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> _is_running; }<br><br> <span class="hljs-comment">// 重传次数</span><br> <span class="hljs-function"><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> <span class="hljs-title">rto_count</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> _rto_count; }<br><br> <span class="hljs-comment">// 慢启动</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">slow_start</span><span class="hljs-params">()</span> </span>{<br> _rto_count++;<br> _rto_timeout *= <span class="hljs-number">2</span>;<br> }<br><br> <span class="hljs-comment">// 更新当前时间</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">update</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">size_t</span> ms_since_last_tick)</span> </span>{ _tick_passed += ms_since_last_tick; }<br><br> <span class="hljs-comment">// 检测是否超时</span><br> <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">is_timeout</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> _is_running && _tick_passed >= _rto_timeout; }<br><br> <span class="hljs-comment">// 重新计时</span><br> <span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">restart</span><span class="hljs-params">()</span> </span>{ _tick_passed = <span class="hljs-number">0</span>; }<br>};<br></code></pre></td></tr></table></figure><p>在<code>private</code>的部分定义则如下:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 超时重传计时器</span><br>TCPTimer _rto_timer{};<br><br><span class="hljs-comment">// 记录确认的_ackno</span><br><span class="hljs-type">size_t</span> _ackno = <span class="hljs-number">0</span>;<br><br><span class="hljs-comment">// 记录窗口大小,并标记是否为空窗口</span><br><span class="hljs-type">size_t</span> _window_size = <span class="hljs-number">1</span>;<br><br><span class="hljs-comment">// 缓存队列</span><br>std::queue<TCPSegment> _cache{};<br></code></pre></td></tr></table></figure><h4 id="额外定义函数"><a class="markdownIt-Anchor" href="#额外定义函数"></a> 额外定义函数</h4><p>额外定义的函数主要作用为将已经封装好的<code>TCP</code>报文进行发送,如果在发送的时候检测到<code>RTO</code>重传计时器并没有工作,则发送的同时激活重传计时器。同时在发送了报文后对seqno序号进行消耗,移动<code>_next_seqno</code>指针</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-type">void</span> TCPSender::_send_segment(<span class="hljs-type">const</span> TCPSegment &seg) {<br> <span class="hljs-comment">// 当前报文需要占用的长度</span><br> <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> seg_len = seg.<span class="hljs-built_in">length_in_sequence_space</span>();<br> _next_seqno += seg_len;<br> _cache.<span class="hljs-built_in">push</span>(seg);<br> _segments_out.<span class="hljs-built_in">push</span>(seg);<br> <span class="hljs-comment">// 如果没启动计时器,就启动计时器</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">not</span> _rto_timer.<span class="hljs-built_in">is_running</span>()) {<br> _rto_timer.<span class="hljs-built_in">run</span>();<br> _rto_timer.<span class="hljs-built_in">reset</span>(_initial_retransmission_timeout);<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="fill_window"><a class="markdownIt-Anchor" href="#fill_window"></a> fill_window()</h4><p>对于需要封装的报文,大致可以分为三类,一类是最开始用于建立连接的<code>SYN</code>报文,一类是携带数据的<code>PAYLOAD</code>报文,最后一类是用于发送结束连接的挥手<code>FIN</code>报文。在该方法中主要的难点就是通过对目前已经确认的<code>ackno</code>和<code>next_seqno</code>等数据来判断当前需要封装的报文具体是哪一类,以及根据还未接收到的数据以及零窗口本身的机制来判断空闲的窗口大小</p><h5 id="fill_space窗口大小"><a class="markdownIt-Anchor" href="#fill_space窗口大小"></a> fill_space窗口大小</h5><p>首先,为了防止出现对方当前空闲窗口已满,而sender就一直啥也不发的情况出现,因此在接受到的窗口大小是0的时候,要将其改为1,来避免零窗口堵塞。同时由于部分数据还在传输的路上,这一部分的数据也需要被减掉,从而得到最后的空闲大小fill_space。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-type">size_t</span> fill_space = _window_size ? _window_size : <span class="hljs-number">1</span>;<br>fill_space -= <span class="hljs-built_in">bytes_in_flight</span>();<br></code></pre></td></tr></table></figure><h5 id="syn报文"><a class="markdownIt-Anchor" href="#syn报文"></a> SYN报文</h5><p><code>SYN</code>报文的判断很简单,因为发送<code>SYN</code>的话无非是打开连接的建立者A自己,又或者是收到了A发来报文的B返回一个携带<code>ACK</code>的<code>SYN</code>报文进行确认。而对于A和B来说,由于<code>SYN</code>报文都是他们自己发送的第一个报文,因此在封装的过程中,他们的“下一个发送序列号”<code>_next_seqno</code>显而易见的应该为零。大致逻辑代码如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// _next_seqno == 0 代表还没有开始发送数据,此时需要发送SYN报文</span><br>section.<span class="hljs-built_in">header</span>().syn = (_next_seqno == <span class="hljs-number">0</span>);<br></code></pre></td></tr></table></figure><h5 id="payload报文"><a class="markdownIt-Anchor" href="#payload报文"></a> PAYLOAD报文</h5><p>对于含有内容的报文,主要的工作就是对<code>payload</code>长度的合理切割,对此只需要在<code>TCPConfig::MAX_PAYLOAD_SIZE</code>和当前剩余``中取最小值并从<code>_stream</code>当中读入。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 将数据进行封装</span><br><span class="hljs-type">size_t</span> segment_payload_size = <span class="hljs-built_in">min</span>(TCPConfig::MAX_PAYLOAD_SIZE, fill_space);<br>section.<span class="hljs-built_in">payload</span>() = _stream.<span class="hljs-built_in">read</span>(segment_payload_size);<br></code></pre></td></tr></table></figure><h5 id="fin报文"><a class="markdownIt-Anchor" href="#fin报文"></a> FIN报文</h5><p>在<code>_stream</code>发送完毕,并且被我方全部接受了的时候发送一个携带<code>FIN</code>的报文,告知对方我方已经发送完毕。由于<code>FIN</code>本身需要消耗一个序列号,因此发送前需检查当前数据段是否还有一个空位来放FIN</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 如果要发送FIN的话,窗口内至少还要剩余一个字符(bytes_in_flight的也会占用窗口)</span><br><span class="hljs-keyword">if</span> (_stream.<span class="hljs-built_in">eof</span>() && fill_space > section.<span class="hljs-built_in">length_in_sequence_space</span>()) {<br> section.<span class="hljs-built_in">header</span>().fin = <span class="hljs-literal">true</span>;<br>}<br></code></pre></td></tr></table></figure><h5 id="发送过滤"><a class="markdownIt-Anchor" href="#发送过滤"></a> 发送过滤</h5><p>在标记完了<code>FIN</code>之后,如果这个报文依旧不占用序列号,则说明这个报文不是<code>TCP Sender</code>处理的部分;又或者此时在<code>FIN</code>已经发送的基础上,重复发送了一个<code>FIN</code>,这时多的<code>FIN</code>应该被抛弃</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 空字符报的报文或错误溢出的报文不应该由`TCP Sender`进行发送</span><br><span class="hljs-keyword">if</span> (section.<span class="hljs-built_in">length_in_sequence_space</span>() == <span class="hljs-number">0</span> || _next_seqno == _stream.<span class="hljs-built_in">bytes_written</span>() + <span class="hljs-number">2</span>) {<br> <span class="hljs-keyword">return</span>;<br>}<br></code></pre></td></tr></table></figure><details><summary>最后总的代码如下</summary><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">TCPSender::fill_window</span><span class="hljs-params">()</span> </span>{<br> <span class="hljs-type">size_t</span> fill_space = _window_size ? _window_size : <span class="hljs-number">1</span>;<br> fill_space -= <span class="hljs-built_in">bytes_in_flight</span>();<br> <span class="hljs-keyword">while</span> (fill_space > <span class="hljs-number">0</span>) {<br> TCPSegment section;<br><br> <span class="hljs-comment">// 发送的数据包的序号是将要写入的下一个序号</span><br> section.<span class="hljs-built_in">header</span>().seqno = <span class="hljs-built_in">next_seqno</span>();<br><br> <span class="hljs-comment">// _next_seqno == 0 代表还没有开始发送数据,此时需要发送SYN报文</span><br> section.<span class="hljs-built_in">header</span>().syn = (_next_seqno == <span class="hljs-number">0</span>);<br><br> <span class="hljs-comment">// 将数据进行封装</span><br> <span class="hljs-type">size_t</span> segment_payload_size = <span class="hljs-built_in">min</span>(TCPConfig::MAX_PAYLOAD_SIZE, fill_space);<br> section.<span class="hljs-built_in">payload</span>() = _stream.<span class="hljs-built_in">read</span>(segment_payload_size);<br><br> <span class="hljs-comment">// 空闲窗口中至少要留有一位序号的位置才能将当前数据包添加FIN(bytes_in_flight的也会占用窗口)</span><br> <span class="hljs-keyword">if</span> (_stream.<span class="hljs-built_in">eof</span>() && fill_space > section.<span class="hljs-built_in">length_in_sequence_space</span>()) {<br> section.<span class="hljs-built_in">header</span>().fin = <span class="hljs-literal">true</span>;<br> }<br><br> <span class="hljs-comment">// 如果这个报文啥都没有,或者FIN报文已经发送了,就没必要发送新的数据段了</span><br> <span class="hljs-keyword">if</span> (section.<span class="hljs-built_in">length_in_sequence_space</span>() == <span class="hljs-number">0</span> || _next_seqno == _stream.<span class="hljs-built_in">bytes_written</span>() + <span class="hljs-number">2</span>) {<br> <span class="hljs-keyword">return</span>;<br> }<br><br> fill_space -= section.<span class="hljs-built_in">length_in_sequence_space</span>();<br><br> _send_segment(section);<br> }<br>}<br></code></pre></td></tr></table></figure></details><h4 id="bytes_in_flight"><a class="markdownIt-Anchor" href="#bytes_in_flight"></a> bytes_in_flight()</h4><p>这个感觉可能是看起来最简单的一个函数了,因为用了<code>_ackno</code>来记录已经确认过的报文,同时<code>_next_seqno</code>又代表的是将要发送的数据流位置,因此只需要将<code>_next_seqno - _ackno</code>返回的就是正在发送中的数据长度了。(最开始想实现的时候还在考虑要不要在每次<code>fill_window</code>和<code>ack_received</code>的时候添加计数器。。)</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">uint64_t</span> <span class="hljs-title">TCPSender::bytes_in_flight</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> _next_seqno - _ackno; }<br></code></pre></td></tr></table></figure><h4 id="ack_received"><a class="markdownIt-Anchor" href="#ack_received"></a> ack_received()</h4><p>确认报文主要需要的逻辑有以下四个部分:</p><ol><li>只处理有效并且正确的<code>ackno</code>。如果<code>ackno</code>有效,记录<code>ackno</code>和<code>window_size</code>用以<code>fill_window()</code>来进行报文的封装</li><li>记录ack报文中包含的窗口大小</li><li>如果曾经的报文已经确认过,则报文已经送达,将送达的报文从缓冲区中弹出,如果所有的报文都被弹出了,则关闭RTO计时器</li><li>如果接受到了对方这时的窗口又有了空闲大小,则使用<code>fill_window()</code>来填充新的空报文</li></ol><h5 id="对于第一个逻辑"><a class="markdownIt-Anchor" href="#对于第一个逻辑"></a> 对于第一个逻辑</h5><p>对于判断<code>ackno</code>是否是正确的<code>ackno</code>,只需要判断<code>ackno</code>是否处于已经记录的<code>_ackno</code>和<code>_next_seqno</code>之间,如果在这个区间之外,意味着要么是老的<code>ackno</code>,要么是确认了不存在的数据,需要进行短路丢弃,逻辑如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-type">uint64_t</span> abs_ackno = <span class="hljs-built_in">unwrap</span>(ackno, _isn, _next_seqno);<br><span class="hljs-comment">// 如果接收到对方发送的确认序号大于自己的下一个序号或者小于自己的已经被确认序号,说明接收到的确认序号是错误的</span><br><span class="hljs-keyword">if</span> (abs_ackno < _ackno || abs_ackno > _next_seqno) {<br> <span class="hljs-keyword">return</span>;<br>}<br></code></pre></td></tr></table></figure><h5 id="对于第二个逻辑"><a class="markdownIt-Anchor" href="#对于第二个逻辑"></a> 对于第二个逻辑</h5><p>在接受到了窗口大小之后只需要直接将其记录</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 记录窗口大小</span><br>_window_size = window_size;<br></code></pre></td></tr></table></figure><h5 id="对于第三-四个逻辑"><a class="markdownIt-Anchor" href="#对于第三-四个逻辑"></a> 对于第三、四个逻辑</h5><p>这部分都是属于对于超时重传的处理,其中主要需要实现的是对缓冲区确认后的报文进行弹出,同时弹出所有报文后取消对<code>RTO</code>的占用,初始化超时重传的等待时间并记录当前的时间。</p><p>其中弹出操作只有在<code>_ackno</code>确认的是第一个报文对应的<code>seqno</code>和<code>length</code>的时候才进行</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 用于判断是否重置计时器</span><br><span class="hljs-type">bool</span> has_reset = <span class="hljs-literal">false</span>;<br><br><span class="hljs-comment">// 当缓冲区内的报文已经被ackno确认,则将已经确认的报文进行丢弃</span><br><span class="hljs-keyword">while</span> (<span class="hljs-keyword">not</span> _cache.<span class="hljs-built_in">empty</span>() &&<br> _cache.<span class="hljs-built_in">front</span>().<span class="hljs-built_in">header</span>().seqno.<span class="hljs-built_in">raw_value</span>() + _cache.<span class="hljs-built_in">front</span>().<span class="hljs-built_in">length_in_sequence_space</span>() <= ackno.<span class="hljs-built_in">raw_value</span>()) {<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">not</span> has_reset) {<br> <span class="hljs-comment">// 有效的确认报文到达,重置计时器</span><br> _rto_timer.<span class="hljs-built_in">reset</span>(_initial_retransmission_timeout);<br> has_reset = <span class="hljs-literal">true</span>;<br> }<br> _cache.<span class="hljs-built_in">pop</span>();<br>}<br><br><span class="hljs-keyword">if</span> (_cache.<span class="hljs-built_in">empty</span>()) {<br> <span class="hljs-comment">// 所有数据包都被确认了,所以暂停计时器</span><br> _rto_timer.<span class="hljs-built_in">stop</span>();<br>}<br><br><span class="hljs-comment">// 如果剩余的窗口还有空间,就填入内容</span><br><span class="hljs-built_in">fill_window</span>();<br></code></pre></td></tr></table></figure><details><summary>最后总的代码如下</summary><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//! \param ackno The remote receiver's ackno (acknowledgment number)</span><br><span class="hljs-comment">//! \param window_size The remote receiver's advertised window size</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">TCPSender::ack_received</span><span class="hljs-params">(<span class="hljs-type">const</span> WrappingInt32 ackno, <span class="hljs-type">const</span> <span class="hljs-type">uint16_t</span> window_size)</span> </span>{<br> <span class="hljs-type">uint64_t</span> abs_ackno = <span class="hljs-built_in">unwrap</span>(ackno, _isn, _next_seqno);<br> <span class="hljs-comment">// 如果接收到对方发送的确认序号大于自己的下一个序号或者小于自己的已经被确认序号,说明接收到的确认序号是错误的</span><br> <span class="hljs-keyword">if</span> (abs_ackno < _ackno || abs_ackno > _next_seqno) {<br> <span class="hljs-keyword">return</span>;<br> }<br> _ackno = abs_ackno;<br><br> <span class="hljs-comment">// 记录窗口大小</span><br> _window_size = window_size;<br><br> <span class="hljs-comment">// 用于判断是否重置计时器</span><br> <span class="hljs-type">bool</span> has_reset = <span class="hljs-literal">false</span>;<br><br> <span class="hljs-comment">// 当缓冲区内的报文已经被ackno确认,则将已经确认的报文进行丢弃</span><br> <span class="hljs-keyword">while</span> (<span class="hljs-keyword">not</span> _cache.<span class="hljs-built_in">empty</span>() &&<br> _cache.<span class="hljs-built_in">front</span>().<span class="hljs-built_in">header</span>().seqno.<span class="hljs-built_in">raw_value</span>() + _cache.<span class="hljs-built_in">front</span>().<span class="hljs-built_in">length_in_sequence_space</span>() <= ackno.<span class="hljs-built_in">raw_value</span>()) {<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">not</span> has_reset) {<br> <span class="hljs-comment">// 有效的确认报文到达,重置计时器</span><br> _rto_timer.<span class="hljs-built_in">reset</span>(_initial_retransmission_timeout);<br> has_reset = <span class="hljs-literal">true</span>;<br> }<br> _cache.<span class="hljs-built_in">pop</span>();<br> }<br><br> <span class="hljs-keyword">if</span> (_cache.<span class="hljs-built_in">empty</span>()) {<br> <span class="hljs-comment">// 所有数据包都被确认了,所以暂停计时器</span><br> _rto_timer.<span class="hljs-built_in">stop</span>();<br> }<br><br> <span class="hljs-comment">// 如果剩余的窗口还有空间,就填入内容</span><br> <span class="hljs-built_in">fill_window</span>();<br>}<br></code></pre></td></tr></table></figure></details><h4 id="tick"><a class="markdownIt-Anchor" href="#tick"></a> tick()</h4><p>该函数主要的作用是推动时间流动,并且判断是否触发超时重传,如果触发了超时重传首先将计时器更新到当前时间。然后当对方窗口不繁忙的情况下(window_size非零)触发了重传就把下次重传的等待时间翻倍,并且记录一次重连;如果对方窗口正处于繁忙期(window_size为零),则不翻倍连接时间。然后再将缓冲区内第一个发送的报文进行重新发送。代码如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//! \param[in] ms_since_last_tick the number of milliseconds since the last call to this method</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">TCPSender::tick</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">size_t</span> ms_since_last_tick)</span> </span>{<br> <span class="hljs-comment">// 更新当前时间</span><br> _rto_timer.<span class="hljs-built_in">update</span>(ms_since_last_tick);<br><br> <span class="hljs-comment">// 检测是否超时</span><br> <span class="hljs-keyword">if</span> ((<span class="hljs-keyword">not</span> _rto_timer.<span class="hljs-built_in">is_timeout</span>())) {<br> <span class="hljs-keyword">return</span>;<br> }<br> <span class="hljs-comment">// 如果上一个收到的报文中,窗口大小不是零,但是依旧超时,说明是网络堵塞,执行慢启动</span><br> <span class="hljs-keyword">if</span> (_window_size != <span class="hljs-number">0</span>) {<br> _rto_timer.<span class="hljs-built_in">slow_start</span>();<br> }<br><br> <span class="hljs-comment">// 重传次数小于最大重传次数,就重传</span><br> <span class="hljs-keyword">if</span> (_rto_timer.<span class="hljs-built_in">rto_count</span>() <= TCPConfig::MAX_RETX_ATTEMPTS) {<br> <span class="hljs-comment">// 发送缓冲区中的第一个报文段</span><br> _segments_out.<span class="hljs-built_in">push</span>(_cache.<span class="hljs-built_in">front</span>());<br> _rto_timer.<span class="hljs-built_in">restart</span>();<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="consecutive_retransmissions"><a class="markdownIt-Anchor" href="#consecutive_retransmissions"></a> consecutive_retransmissions()</h4><p>这个函数就是直接返回次数的,直接返回<code>_rto_timer.rto_count();</code>的大小即可。</p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>cs144</tag>
<tag>network</tag>
</tags>
</entry>
<entry>
<title>CS144-Lab2 计算机网络:TCP Receiver的实现</title>
<link href="/p/4e68707.html"/>
<url>/p/4e68707.html</url>
<content type="html"><![CDATA[<h2 id="tcp-receiver"><a class="markdownIt-Anchor" href="#tcp-receiver"></a> TCP Receiver</h2><h3 id="index和seqno的转换"><a class="markdownIt-Anchor" href="#index和seqno的转换"></a> Index和Seqno的转换</h3><p>为了节省在<code>TCP Header</code>当中的空间,在<code>StreamReassembler</code>里面写的<code>index</code>虽然是一个<code>uint64_t</code>的类型,但是在实际的<code>Header</code>中是使用一个<code>uint32_t</code>的<code>seqno</code>来进行标记位置的。对于<code>uint32_t</code>的<code>seqno</code>和<code>uint64_t</code>的<code>index</code>的相互转换则是通过以<code>4GiB (2^32 bytes)</code>为一个长度进行取模来实现。</p><p>同时为了提高<code>TCP</code>本身的安全性,并且确保每次获得的<code>segments</code>数据段都是来自于本次连接的,因此提出了<code>ISN(Initial Sequence Number)</code>的概念,即本次链接是从序号为<code>isn</code>开始作为<code>seqno</code>进行通信,大于<code>isn</code>的<code>seqno</code>所代表的<code>index</code>是本次链接所需要的数据段,早于<code>isn</code>的<code>seqno</code>则是来自于上一次连接的老数据段,并不需要处理。</p><p>如果想要将<code>uint32_t</code>的<code>seqno</code>转为一个<code>uint64_t</code>则需要一个<code>checkpoint</code>作为定位,防止<code>seqno</code>被定位到错误的位置上。这个<code>checkpoint</code>在实现中就是最后一个重新组装后的字符位置</p><blockquote><p>按lab2的原文:In your TCP implementation, you’ll use the index of the last reassembled byte as the checkpoint.</p></blockquote><p>通过寻找距离<code>checkpoint</code>最近的<code>seqno</code>就可以定位到本来需要插入的<code>seqno</code>位置了</p><h4 id="代码思路"><a class="markdownIt-Anchor" href="#代码思路"></a> 代码思路</h4><p>对于将<code>uint32_t</code>转为<code>uint64_t</code>的代码实现很简单,只需要将<code>uint64_t</code>的<code>index</code>加上<code>isn</code>的值之后对<code>2^32</code>进行取模就行了,具体代码实现如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function">WrappingInt32 <span class="hljs-title">wrap</span><span class="hljs-params">(<span class="hljs-type">uint64_t</span> n, WrappingInt32 isn)</span> </span>{<br> <span class="hljs-type">uint64_t</span> result = (n + isn.<span class="hljs-built_in">raw_value</span>()) % (<span class="hljs-built_in">static_cast</span><<span class="hljs-type">uint64_t</span>>(UINT32_MAX) + <span class="hljs-number">1</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">WrappingInt32</span>(<span class="hljs-built_in">static_cast</span><<span class="hljs-type">uint32_t</span>>(result));<br>}<br></code></pre></td></tr></table></figure><p>而对于将<code>wrap</code>后的<code>seqno</code>转回<code>index</code>,我直接通过类似分类讨论的枚举找到了四个临界点,只需要判断<code>checkpoint</code>相对于临界点的位置就可以得到答案。代码如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">uint64_t</span> <span class="hljs-title">unwrap</span><span class="hljs-params">(WrappingInt32 n, WrappingInt32 isn, <span class="hljs-type">uint64_t</span> checkpoint)</span> </span>{<br> <span class="hljs-type">const</span> <span class="hljs-type">uint64_t</span> L = (<span class="hljs-number">1ul</span> << <span class="hljs-number">32</span>);<br> <span class="hljs-type">const</span> <span class="hljs-type">uint64_t</span> a = (checkpoint / L) * L - isn.<span class="hljs-built_in">raw_value</span>() + n.<span class="hljs-built_in">raw_value</span>();<br> <span class="hljs-keyword">if</span> (checkpoint > a + (L * <span class="hljs-number">3</span>) / <span class="hljs-number">2</span>) {<br> <span class="hljs-keyword">return</span> a + <span class="hljs-number">2</span> * L;<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (checkpoint > a + L / <span class="hljs-number">2</span>) {<br> <span class="hljs-keyword">return</span> a + L;<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (checkpoint < L) {<br> <span class="hljs-keyword">return</span> n.<span class="hljs-built_in">raw_value</span>() < isn.<span class="hljs-built_in">raw_value</span>() ? a + L : a;<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> checkpoint < a - L / <span class="hljs-number">2</span> ? a - L : a;<br> }<br>}<br></code></pre></td></tr></table></figure><h5 id="详细思路点这里硬分类感觉好蠢但是有效jpg"><a class="markdownIt-Anchor" href="#详细思路点这里硬分类感觉好蠢但是有效jpg"></a> 详细思路点这里(硬分类,感觉好蠢,但是有效.jpg)</h5><details><summary>通解推导</summary><h5 id="checkpoint-l"><a class="markdownIt-Anchor" href="#checkpoint-l"></a> checkpoint > L</h5><p>由以下公式</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mrow><mo fence="true">(</mo><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><mi>x</mi><mo>+</mo><mi>i</mi><mi>s</mi><mi>n</mi><mo fence="true">)</mo></mrow><mspace></mspace><mspace width="1em"/><mrow><mi mathvariant="normal">m</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">d</mi></mrow><mtext> </mtext><mtext> </mtext><mi>L</mi><mo>=</mo><mi>s</mi><mi>e</mi><mi>q</mi><mi>n</mi><mi>o</mi></mrow><annotation encoding="application/x-tex">\left ( index+isn \right )\mod{ L } = seqno</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord mathdefault">x</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">s</span><span class="mord mathdefault">n</span><span class="mclose delimcenter" style="top:0em;">)</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mspace allowbreak"></span><span class="mspace" style="margin-right:1em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord"><span class="mord"><span class="mord mathrm">m</span><span class="mord mathrm">o</span><span class="mord mathrm">d</span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">s</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.03588em;">q</span><span class="mord mathdefault">n</span><span class="mord mathdefault">o</span></span></span></span></span></p><p>通过推导可以得到</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><mi>x</mi><mo>=</mo><mi>s</mi><mi>e</mi><mi>q</mi><mi>n</mi><mi>o</mi><mo>+</mo><mi>k</mi><mo>∗</mo><mi>L</mi><mo>−</mo><mi>i</mi><mi>s</mi><mi>n</mi></mrow><annotation encoding="application/x-tex">index = seqno + k * L - isn</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord mathdefault">x</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.7777700000000001em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">s</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.03588em;">q</span><span class="mord mathdefault">n</span><span class="mord mathdefault">o</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.65952em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">s</span><span class="mord mathdefault">n</span></span></span></span></span></p><p>因此如果需要得到离<code>checkpoint</code>最近的<code>index</code>就只需要找到合适的<code>k</code>即可,在这里不妨设</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>m</mi><mo>=</mo><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi><mi mathvariant="normal">/</mi><mi>L</mi></mrow><annotation encoding="application/x-tex">m = checkpoint / L</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathdefault">m</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span><span class="mord">/</span><span class="mord mathdefault">L</span></span></span></span></span></p><p>取<code>m</code>作为一个附近值,通过画图可以知道,在一般情况下,答案一定在<code>checkpoint</code>附近的三个区间内</p><p><img src="https://lsky.halc.top/SclwTP.png" alt="k的范围" /></p><h6 id="seqno-isn-0"><a class="markdownIt-Anchor" href="#seqno-isn-0"></a> seqno - isn > 0</h6><p>在这种情况下,<code>checkpoint</code>的前中后三个区间都存在,只要列举并讨论范围就很简单了</p><p>当<code>seqno - isn</code>为正数的时候,<code>index</code>可能的一个取值会落在第②个区间上,有</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mrow><mo mathvariant="normal">′</mo><mo mathvariant="normal">′</mo></mrow></msup><mo>=</mo><mi>m</mi><mo>∗</mo><mi>L</mi><mo>+</mo><mi>s</mi><mi>e</mi><mi>q</mi><mi>n</mi><mi>o</mi><mo>−</mo><mi>i</mi><mi>s</mi><mi>n</mi></mrow><annotation encoding="application/x-tex">index'' = m * L + seqno - isn</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.801892em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.46528em;vertical-align:0em;"></span><span class="mord mathdefault">m</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.7777700000000001em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">s</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.03588em;">q</span><span class="mord mathdefault">n</span><span class="mord mathdefault">o</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.65952em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">s</span><span class="mord mathdefault">n</span></span></span></span></span></p><p>此时第①区间和第③区间上的<code>index</code>可以分别表示为</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mo mathvariant="normal">′</mo></msup><mo>=</mo><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mrow><mo mathvariant="normal">′</mo><mo mathvariant="normal">′</mo></mrow></msup><mo>−</mo><mi>L</mi><mspace linebreak="newline"></mspace><mspace linebreak="newline"></mspace><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mrow><mo mathvariant="normal">′</mo><mo mathvariant="normal">′</mo><mo mathvariant="normal">′</mo></mrow></msup><mo>=</mo><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mrow><mo mathvariant="normal">′</mo><mo mathvariant="normal">′</mo></mrow></msup><mo>+</mo><mi>L</mi></mrow><annotation encoding="application/x-tex">index' = index'' - L \\\\index''' = index'' + L</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.801892em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8852220000000001em;vertical-align:-0.08333em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathdefault">L</span></span><span class="mspace newline"></span><span class="mspace newline"></span><span class="base"><span class="strut" style="height:0.801892em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span><span class="mord mtight">′</span><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8852220000000001em;vertical-align:-0.08333em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathdefault">L</span></span></span></span></span></p><p>对<code>index'</code>、<code>index''</code>和<code>index'''</code>的中间值进行判断,很容易得到以下规律</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mtable rowspacing="0.15999999999999992em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>=</mo><mi>m</mi><mo>∗</mo><mi>L</mi><mo>+</mo><mi>s</mi><mi>e</mi><mi>q</mi><mi>n</mi><mi>o</mi><mo>−</mo><mi>i</mi><mi>s</mi><mi>n</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>L</mi><mo>=</mo><mi>U</mi><mi>I</mi><mi>N</mi><mi>T</mi><mn>32</mn><mi mathvariant="normal">_</mi><mi>M</mi><mi>A</mi><mi>X</mi><mo>+</mo><mn>1</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo fence="true">{</mo><mtable rowspacing="0.15999999999999992em" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi><mo><</mo><mi>a</mi><mo>−</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a-L</mtext></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>−</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn><mo>≤</mo><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi><mo><</mo><mi>a</mi><mo>+</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a</mtext></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>+</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn><mo>≤</mo><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a+L</mtext></mstyle></mtd></mtr></mtable></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{array}{l} a = m*L+seqno-isn \\\\ L = UINT32\_MAX+1 \\\\ \left \{\begin{matrix} checkpoint < a - L/2 & \text{index=a-L}\\\\ a-L/2\leq checkpoint< a+L/2 & \text{index=a}\\\\ a+L/2\leq checkpoint & \text{index=a+L}\end{matrix}\right.\end{array}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:10.80004em;vertical-align:-5.1500200000000005em;"></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:5.65002em;"><span style="top:-10.060039999999997em;"><span class="pstrut" style="height:5.250019999999999em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">m</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">s</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.03588em;">q</span><span class="mord mathdefault">n</span><span class="mord mathdefault">o</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">s</span><span class="mord mathdefault">n</span></span></span><span style="top:-8.860039999999998em;"><span class="pstrut" style="height:5.250019999999999em;"></span><span class="mord"></span></span><span style="top:-7.660039999999999em;"><span class="pstrut" style="height:5.250019999999999em;"></span><span class="mord"><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault" style="margin-right:0.10903em;">U</span><span class="mord mathdefault" style="margin-right:0.07847em;">I</span><span class="mord mathdefault" style="margin-right:0.10903em;">N</span><span class="mord mathdefault" style="margin-right:0.13889em;">T</span><span class="mord">3</span><span class="mord">2</span><span class="mord" style="margin-right:0.02778em;">_</span><span class="mord mathdefault" style="margin-right:0.10903em;">M</span><span class="mord mathdefault">A</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span></span></span><span style="top:-6.4600399999999985em;"><span class="pstrut" style="height:5.250019999999999em;"></span><span class="mord"></span></span><span style="top:-2.850019999999999em;"><span class="pstrut" style="height:5.250019999999999em;"></span><span class="mord"><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.2500199999999997em;"><span style="top:-1.2999899999999998em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎩</span></span></span><span style="top:-1.2999899999999998em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-1.5999899999999998em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-1.8999899999999998em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-2.1999899999999997em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-3.15001em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎨</span></span></span><span style="top:-4.30001em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-4.60001em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-4.90001em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-5.20001em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-5.500019999999999em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎧</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:2.75002em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.2500000000000004em;"><span style="top:-5.410000000000001em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel"><</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span></span></span><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"></span></span><span style="top:-3.01em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel"><</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span></span></span><span style="top:-1.8099999999999998em;"><span class="pstrut" style="height:3em;"></span><span class="mord"></span></span><span style="top:-0.6099999999999997em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:2.7500000000000004em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.2500000000000004em;"><span style="top:-5.410000000000001em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a-L</span></span></span></span><span style="top:-3.0100000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a</span></span></span></span><span style="top:-0.6100000000000001em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a+L</span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:2.75em;"><span></span></span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:5.1500200000000005em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span></span></span></span></span></p><blockquote><p>注:此时checkpoint一定小于 a+L,因为a+L属于第③区间,而checkpoint在第②区间内</p></blockquote><h6 id="seqno-isn-0-2"><a class="markdownIt-Anchor" href="#seqno-isn-0-2"></a> seqno - isn < 0</h6><p>此时因为是从<code>m*L</code>的位置向前移动,所以相比于上面,三个可能是答案的<code>index</code>的分布则改为了</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mo mathvariant="normal">′</mo></msup><mo>=</mo><mi>m</mi><mo>∗</mo><mi>L</mi><mo>+</mo><mi>s</mi><mi>e</mi><mi>q</mi><mi>n</mi><mi>o</mi><mo>−</mo><mi>i</mi><mi>s</mi><mi>n</mi><mspace linebreak="newline"></mspace><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mrow><mo mathvariant="normal">′</mo><mo mathvariant="normal">′</mo></mrow></msup><mo>=</mo><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mo mathvariant="normal">′</mo></msup><mo>+</mo><mi>L</mi><mspace linebreak="newline"></mspace><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mrow><mo mathvariant="normal">′</mo><mo mathvariant="normal">′</mo><mo mathvariant="normal">′</mo></mrow></msup><mo>=</mo><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><msup><mi>x</mi><mo mathvariant="normal">′</mo></msup><mo>+</mo><mn>2</mn><mo>∗</mo><mi>L</mi></mrow><annotation encoding="application/x-tex">index' = m * L + seqno - isn\\index'' = index' + L\\index''' = index' + 2 * L</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.801892em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.46528em;vertical-align:0em;"></span><span class="mord mathdefault">m</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.7777700000000001em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">s</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.03588em;">q</span><span class="mord mathdefault">n</span><span class="mord mathdefault">o</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.65952em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">s</span><span class="mord mathdefault">n</span></span><span class="mspace newline"></span><span class="base"><span class="strut" style="height:0.801892em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8852220000000001em;vertical-align:-0.08333em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathdefault">L</span></span><span class="mspace newline"></span><span class="base"><span class="strut" style="height:0.801892em;vertical-align:0em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span><span class="mord mtight">′</span><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.8852220000000001em;vertical-align:-0.08333em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord"><span class="mord mathdefault">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.801892em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathdefault">L</span></span></span></span></span></p><p>所以很容易得到以下结果</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mtable rowspacing="0.15999999999999992em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>=</mo><mi>m</mi><mo>∗</mo><mi>L</mi><mo>+</mo><mi>s</mi><mi>e</mi><mi>q</mi><mi>n</mi><mi>o</mi><mo>−</mo><mi>i</mi><mi>s</mi><mi>n</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>L</mi><mo>=</mo><mi>U</mi><mi>I</mi><mi>N</mi><mi>T</mi><mn>32</mn><mi mathvariant="normal">_</mi><mi>M</mi><mi>A</mi><mi>X</mi><mo>+</mo><mn>1</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo fence="true">{</mo><mtable rowspacing="0.15999999999999992em" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi><mo><</mo><mi>a</mi><mo>+</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a</mtext></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>+</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn><mo>≤</mo><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi><mo><</mo><mi>a</mi><mo>+</mo><mo stretchy="false">(</mo><mn>3</mn><mo>∗</mo><mi>L</mi><mo stretchy="false">)</mo><mi mathvariant="normal">/</mi><mn>2</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a + L</mtext></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>+</mo><mo stretchy="false">(</mo><mn>3</mn><mo>∗</mo><mi>L</mi><mo stretchy="false">)</mo><mi mathvariant="normal">/</mi><mn>2</mn><mo>≤</mo><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a+2*L</mtext></mstyle></mtd></mtr></mtable></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{array}{l} a = m*L+seqno-isn\\ L = UINT32\_MAX+1\\ \left \{\begin{matrix} checkpoint < a + L/2 & \text{index=a}\\ a+L/2\leq checkpoint< a+(3*L)/2 & \text{index=a + L}\\ a+(3*L)/2\leq checkpoint & \text{index=a+2*L}\end{matrix}\right.\end{array}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:6.00004em;vertical-align:-2.7500199999999997em;"></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.2500200000000006em;"><span style="top:-6.46004em;"><span class="pstrut" style="height:4.05002em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">m</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">s</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.03588em;">q</span><span class="mord mathdefault">n</span><span class="mord mathdefault">o</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">s</span><span class="mord mathdefault">n</span></span></span><span style="top:-5.260040000000001em;"><span class="pstrut" style="height:4.05002em;"></span><span class="mord"><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault" style="margin-right:0.10903em;">U</span><span class="mord mathdefault" style="margin-right:0.07847em;">I</span><span class="mord mathdefault" style="margin-right:0.10903em;">N</span><span class="mord mathdefault" style="margin-right:0.13889em;">T</span><span class="mord">3</span><span class="mord">2</span><span class="mord" style="margin-right:0.02778em;">_</span><span class="mord mathdefault" style="margin-right:0.10903em;">M</span><span class="mord mathdefault">A</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span></span></span><span style="top:-2.85002em;"><span class="pstrut" style="height:4.05002em;"></span><span class="mord"><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05002em;"><span style="top:-2.49999em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎩</span></span></span><span style="top:-3.15001em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎨</span></span></span><span style="top:-4.30002em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎧</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:1.55002em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel"><</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span></span></span><span style="top:-3.0099999999999993em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel"><</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mopen">(</span><span class="mord">3</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mclose">)</span><span class="mord">/</span><span class="mord">2</span></span></span><span style="top:-1.8099999999999994em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mopen">(</span><span class="mord">3</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mclose">)</span><span class="mord">/</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:1.5500000000000007em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.05em;"><span style="top:-4.21em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a</span></span></span></span><span style="top:-3.0099999999999993em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a + L</span></span></span></span><span style="top:-1.8099999999999994em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a+2*L</span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:1.5500000000000007em;"><span></span></span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:2.7500199999999997em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span></span></span></span></span></p><p>将以上两种规律整合,我们很容易可以得到以下通解</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mtable rowspacing="0.15999999999999992em" columnalign="left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>=</mo><mi>m</mi><mo>∗</mo><mi>L</mi><mo>+</mo><mi>s</mi><mi>e</mi><mi>q</mi><mi>n</mi><mi>o</mi><mo>−</mo><mi>i</mi><mi>s</mi><mi>n</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>L</mi><mo>=</mo><mi>U</mi><mi>I</mi><mi>N</mi><mi>T</mi><mn>32</mn><mi mathvariant="normal">_</mi><mi>M</mi><mi>A</mi><mi>X</mi><mo>+</mo><mn>1</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo fence="true">{</mo><mtable rowspacing="0.15999999999999992em" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi><mo><</mo><mi>a</mi><mo>−</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a-L</mtext></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>−</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn><mo>≤</mo><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi><mo><</mo><mi>a</mi><mo>+</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a</mtext></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>+</mo><mi>L</mi><mi mathvariant="normal">/</mi><mn>2</mn><mo>≤</mo><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi><mo><</mo><mi>a</mi><mo>+</mo><mo stretchy="false">(</mo><mn>3</mn><mo>∗</mo><mi>L</mi><mo stretchy="false">)</mo><mi mathvariant="normal">/</mi><mn>2</mn></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a + L</mtext></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>a</mi><mo>+</mo><mo stretchy="false">(</mo><mn>3</mn><mo>∗</mo><mi>L</mi><mo stretchy="false">)</mo><mi mathvariant="normal">/</mi><mn>2</mn><mo>≤</mo><mi>c</mi><mi>h</mi><mi>e</mi><mi>c</mi><mi>k</mi><mi>p</mi><mi>o</mi><mi>i</mi><mi>n</mi><mi>t</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>index=a+2*L</mtext></mstyle></mtd></mtr></mtable></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{array}{l} a = m*L+seqno-isn\\ L = UINT32\_MAX+1\\ \left \{\begin{matrix} checkpoint < a - L/2 & \text{index=a-L}\\ a-L/2\leq checkpoint< a+L/2 & \text{index=a}\\ a+L/2\leq checkpoint< a+(3*L)/2 & \text{index=a + L}\\ a+(3*L)/2\leq checkpoint & \text{index=a+2*L}\end{matrix}\right.\end{array}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:7.2000399999999996em;vertical-align:-3.3500199999999993em;"></span><span class="mord"><span class="mtable"><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.85002em;"><span style="top:-7.6600399999999995em;"><span class="pstrut" style="height:4.65002em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">m</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">s</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.03588em;">q</span><span class="mord mathdefault">n</span><span class="mord mathdefault">o</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">s</span><span class="mord mathdefault">n</span></span></span><span style="top:-6.46004em;"><span class="pstrut" style="height:4.65002em;"></span><span class="mord"><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault" style="margin-right:0.10903em;">U</span><span class="mord mathdefault" style="margin-right:0.07847em;">I</span><span class="mord mathdefault" style="margin-right:0.10903em;">N</span><span class="mord mathdefault" style="margin-right:0.13889em;">T</span><span class="mord">3</span><span class="mord">2</span><span class="mord" style="margin-right:0.02778em;">_</span><span class="mord mathdefault" style="margin-right:0.10903em;">M</span><span class="mord mathdefault">A</span><span class="mord mathdefault" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span></span></span><span style="top:-3.4500200000000003em;"><span class="pstrut" style="height:4.65002em;"></span><span class="mord"><span class="minner"><span class="mopen"><span class="delimsizing mult"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.65002em;"><span style="top:-1.8999899999999998em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎩</span></span></span><span style="top:-1.8999899999999998em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-2.1999899999999997em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-3.15001em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎨</span></span></span><span style="top:-4.30001em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-4.60001em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎪</span></span></span><span style="top:-4.90002em;"><span class="pstrut" style="height:3.15em;"></span><span class="delimsizinginner delim-size4"><span>⎧</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:2.15002em;"><span></span></span></span></span></span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.6500000000000004em;"><span style="top:-4.8100000000000005em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel"><</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel"><</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span></span></span><span style="top:-2.4099999999999997em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mord">/</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel"><</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mopen">(</span><span class="mord">3</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mclose">)</span><span class="mord">/</span><span class="mord">2</span></span></span><span style="top:-1.2099999999999997em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">a</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mopen">(</span><span class="mord">3</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">L</span><span class="mclose">)</span><span class="mord">/</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mord mathdefault">c</span><span class="mord mathdefault">h</span><span class="mord mathdefault">e</span><span class="mord mathdefault">c</span><span class="mord mathdefault" style="margin-right:0.03148em;">k</span><span class="mord mathdefault">p</span><span class="mord mathdefault">o</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">t</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:2.1500000000000004em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span><span class="arraycolsep" style="width:0.5em;"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.6500000000000004em;"><span style="top:-4.8100000000000005em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a-L</span></span></span></span><span style="top:-3.61em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a</span></span></span></span><span style="top:-2.4099999999999997em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a + L</span></span></span></span><span style="top:-1.2099999999999997em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord text"><span class="mord">index=a+2*L</span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:2.1500000000000004em;"><span></span></span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:3.3500199999999993em;"><span></span></span></span></span></span><span class="arraycolsep" style="width:0.5em;"></span></span></span></span></span></span></span></p></details><details><summary>特殊情况</summary><h5 id="checkpoint-l-2"><a class="markdownIt-Anchor" href="#checkpoint-l-2"></a> checkpoint < L</h5><p>在<code>checkpoint < L</code>的时候,通解中对于<code>a - L</code>的一部分(即checkpoint < a + L)就不适用了,不过分析起来也很简单,由于有</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mrow><mo fence="true">(</mo><mi>i</mi><mi>n</mi><mi>d</mi><mi>e</mi><mi>x</mi><mo>+</mo><mi>i</mi><mi>s</mi><mi>n</mi><mo fence="true">)</mo></mrow><mspace></mspace><mspace width="1em"/><mrow><mi mathvariant="normal">m</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">d</mi></mrow><mtext> </mtext><mtext> </mtext><mi>L</mi><mo>=</mo><mi>s</mi><mi>e</mi><mi>q</mi><mi>n</mi><mi>o</mi></mrow><annotation encoding="application/x-tex">\left ( index+isn \right )\mod{ L } = seqno</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord mathdefault">i</span><span class="mord mathdefault">n</span><span class="mord mathdefault">d</span><span class="mord mathdefault">e</span><span class="mord mathdefault">x</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">s</span><span class="mord mathdefault">n</span><span class="mclose delimcenter" style="top:0em;">)</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mspace allowbreak"></span><span class="mspace" style="margin-right:1em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord"><span class="mord"><span class="mord mathrm">m</span><span class="mord mathrm">o</span><span class="mord mathrm">d</span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathdefault">L</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">s</span><span class="mord mathdefault">e</span><span class="mord mathdefault" style="margin-right:0.03588em;">q</span><span class="mord mathdefault">n</span><span class="mord mathdefault">o</span></span></span></span></span></p><p>所以当<code>seqno</code>小于<code>isn</code>的时候,答案一定在下一个区间,因此答案即<code>L - isn + seqno</code>,当<code>seqno</code>大于<code>isn</code>且<code>checkpoint < a + L</code>,所以答案一定为<code>a</code></p><p>所以就可以得到上述代码了。</p></details><h3 id="tcp-段接收处理"><a class="markdownIt-Anchor" href="#tcp-段接收处理"></a> TCP 段接收处理</h3><p>这部分代码逻辑完成的是<code>tcp</code>握手中对于<code>tcp</code>段的接受处理。</p><p>我自己增加的私有成员和用途大致为:</p><ul><li><code>_is_syn</code>: 判断链接是否建立</li><li><code>_isn</code>: 存入第一次建立连接时接受的<code>seqno</code>来初始化</li><li><code>_is_fin</code>: 用于判断结束输入的报文是否传入</li></ul><p>对于<code>ackno</code>和<code>checkpoint</code>的实现机制是:</p><ul><li><code>ackno</code>: 本质上就是返回已经整合好的数据量,也就是<code>bytes_stream</code>的<code>bytes_written()</code>,同时建立连接后一定存在<code>syn</code>所以可以直接加一,之后只需要判断<code>fin</code>是否到达并且整合完毕,然后再次加一即可。</li><li><code>checkpoint</code>: 和<code>ackno</code>差别不大,只需要直接返回已经写入完成的字符个数即可</li></ul><p>知道了上述几个逻辑以后就只需要通过调整简单的逻辑<code>flag</code>加上<code>lab1</code>里面的<code>push_substring</code>来对<code>payload()</code>进行整合就可以通过了。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">TCPReceiver::segment_received</span><span class="hljs-params">(<span class="hljs-type">const</span> TCPSegment &seg)</span> </span>{<br> <span class="hljs-comment">// 等待并处理第一个syn链接</span><br> <span class="hljs-keyword">if</span> ((_is_syn == <span class="hljs-number">0</span>) && seg.<span class="hljs-built_in">header</span>().syn) {<br> _is_syn = <span class="hljs-number">1</span>;<br> _isn = seg.<span class="hljs-built_in">header</span>().seqno;<br> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (_is_syn == <span class="hljs-number">0</span>) {<br> <span class="hljs-keyword">return</span>;<br> }<br><br> <span class="hljs-comment">// checkpoint的位置就是已经写入完成的字符的数量</span><br> <span class="hljs-comment">// In your TCP implementation, you’ll use the index of the last reassembled byte as the checkpoint.</span><br> <span class="hljs-type">const</span> <span class="hljs-type">uint64_t</span> checkpoint = _reassembler.<span class="hljs-built_in">stream_out</span>().<span class="hljs-built_in">bytes_written</span>() + <span class="hljs-number">1</span>;<br><br> <span class="hljs-comment">// 将内容写入reassembler,其中之所以要有(- 1 + seg.header().syn)这个部分,是因为当握手成功以后</span><br> <span class="hljs-comment">// seqno是从1开始的,而没有握手的时候stream_index应该将包含syn的报文写在index为0的位置上</span><br> <span class="hljs-type">uint64_t</span> stream_index = <span class="hljs-built_in">unwrap</span>(seg.<span class="hljs-built_in">header</span>().seqno, _isn, checkpoint) - <span class="hljs-number">1</span> + seg.<span class="hljs-built_in">header</span>().syn;<br> _reassembler.<span class="hljs-built_in">push_substring</span>(seg.<span class="hljs-built_in">payload</span>().<span class="hljs-built_in">copy</span>(), stream_index, seg.<span class="hljs-built_in">header</span>().fin);<br><br> <span class="hljs-comment">// 标志结尾的TCP段是否送达</span><br> <span class="hljs-keyword">if</span> (seg.<span class="hljs-built_in">header</span>().fin) {<br> _is_fin = <span class="hljs-number">1</span>;<br> }<br>}<br><br><span class="hljs-function">optional<WrappingInt32> <span class="hljs-title">TCPReceiver::ackno</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{<br> <span class="hljs-comment">// 返回已经消耗的index长度,也就是ackno确认了的长度</span><br> WrappingInt32 result = _isn + _is_syn + _reassembler.<span class="hljs-built_in">stream_out</span>().<span class="hljs-built_in">bytes_written</span>();<br> <span class="hljs-keyword">if</span> ((_is_fin != <span class="hljs-number">0</span>) && _reassembler.<span class="hljs-built_in">unassembled_bytes</span>() == <span class="hljs-number">0</span>) {<br> <span class="hljs-comment">// 判断是否包含结束的报文</span><br> result = result + _is_fin;<br> }<br> <span class="hljs-comment">// 如果建立了链接才返回ackno,在建立报文之前是没有ackno的,因为没有对方的信息可以让自己确认</span><br> <span class="hljs-keyword">return</span> _is_syn ? <span class="hljs-built_in">optional</span><WrappingInt32>(result) : <span class="hljs-literal">nullopt</span>;<br>}<br><br><span class="hljs-function"><span class="hljs-type">size_t</span> <span class="hljs-title">TCPReceiver::window_size</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> _capacity - _reassembler.<span class="hljs-built_in">stream_out</span>().<span class="hljs-built_in">buffer_size</span>(); }<br></code></pre></td></tr></table></figure><p>这里有一个让我感觉很疑惑的点就是在单元测试中存在两种测试样例,这里做个记录,后面如果知道了原因就来解决一下</p><ul><li>存在同时携带<code>SYN</code>和<code>FIN</code>报文,按照正常的TCP握手感觉这是不合理的</li><li>在接受<code>SYN</code>的同时会接受一部分的<code>Data</code>进行处理,按正常的TCP也是不会这么做的</li></ul>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>cs144</tag>
<tag>network</tag>
</tags>
</entry>
<entry>
<title>CS144-Lab1 计算机网络:字节流重组器</title>
<link href="/p/aeda2510.html"/>
<url>/p/aeda2510.html</url>
<content type="html"><![CDATA[<h2 id="思路总结"><a class="markdownIt-Anchor" href="#思路总结"></a> 思路总结</h2><h3 id="有问题的方案"><a class="markdownIt-Anchor" href="#有问题的方案"></a> 有问题的方案</h3><p>这个方案是采用了一个无限长的字符串<code>cache</code>,所有的TCP段中的部分数据先寄存在<code>cache</code>当中。之后通过创建一个在<code>cache</code>上滑动的写入位指针<code>write_p</code>来将能够顺序写入的内容写入<code>_output</code>当中,其中<code>write_p</code>每次滑动的距离<code>len</code>受限于<code>_output</code>还剩下的可容纳空间。</p><p><img src="https://lsky.halc.top/S7h6KM.png" alt="cache_slide" /></p><p>添加的私有成员:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 用于存放缓存</span><br>std::string cache;<br><span class="hljs-comment">// 用于标记缓存对应字节上是否写入内容</span><br>std::string dirty_check;<br><span class="hljs-comment">// 标记写入指针</span><br><span class="hljs-type">size_t</span> write_p;<br><span class="hljs-comment">// 标记EOF位</span><br><span class="hljs-type">size_t</span> end_p;<br></code></pre></td></tr></table></figure><p>对于<code>push_string</code>方法的实现:</p><ol><li><p>检查传入的index是否在可写入范围,如果超出可写入范围则直接退出,保证程序的鲁棒性</p></li><li><p>因为写入的数据长度不能超过<code>capacity</code>,因此需要将扩容的长度设置为<code>index + data.length()</code>和<code>write_p + _output.remaining_capacity()</code>中较小的那个</p></li><li><p>将传入的数据(包括可能超过范围的部分)写入<code>cache</code>中,同时将<code>dirty_check</code>中对应的位置标记为<code>1</code></p></li><li><p>将<code>cache</code>的长度缩回到正确扩容后应该的长度,这样可以将多余的内容丢弃</p></li><li><p>检查<code>write_p</code>的位置上是否有数据可以被写入,如果有则通过滑动<code>len</code>来将内容写入<code>_output</code>,否则跳过</p></li><li><p>检查<code>write_p</code>和<code>end_p</code>是否相同,如果相同则代表写入结束,调用<code>_output.end_input()</code></p></li></ol><p>具体代码:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//! \details This function accepts a substring (aka a segment) of bytes,</span><br><span class="hljs-comment">//! possibly out-of-order, from the logical stream, and assembles any newly</span><br><span class="hljs-comment">//! contiguous substrings and writes them into the output stream in order.</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">StreamReassembler::push_substring</span><span class="hljs-params">(<span class="hljs-type">const</span> string &data, <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> index, <span class="hljs-type">const</span> <span class="hljs-type">bool</span> eof)</span> </span>{<br> <span class="hljs-comment">// extend_size: 按照index和data.length()扩容后的大小,只会按扩大的来扩容</span><br> <span class="hljs-type">size_t</span> extend_size = index + data.<span class="hljs-built_in">length</span>();<br><br> <span class="hljs-comment">// 记录EOF的位置</span><br> <span class="hljs-keyword">if</span> (eof) {<br> end_p = extend_size;<br> }<br><br> <span class="hljs-comment">// 扩容只会变大,不会缩小</span><br> <span class="hljs-keyword">if</span> (extend_size > cache.<span class="hljs-built_in">length</span>()) {<br> cache.<span class="hljs-built_in">resize</span>(extend_size);<br> dirty_check.<span class="hljs-built_in">resize</span>(extend_size);<br> }<br><br> <span class="hljs-comment">// 将要排序的内容写入cache当中</span><br> cache.<span class="hljs-built_in">replace</span>(index, data.<span class="hljs-built_in">length</span>(), data);<br> dirty_check.<span class="hljs-built_in">replace</span>(index, data.<span class="hljs-built_in">length</span>(), data.<span class="hljs-built_in">length</span>(), <span class="hljs-string">'1'</span>);<br><br> <span class="hljs-comment">// 缩回原来的大小,将缓冲区外多余的内容丢弃</span><br> <span class="hljs-keyword">if</span> (expand_size > cache_raw_length) {<br> cache.<span class="hljs-built_in">resize</span>(expand_size);<br> dirty_check.<span class="hljs-built_in">resize</span>(expand_size);<br> }<br><br> <span class="hljs-comment">// 检查写入位上是否有字符,有字符则通过滑动len来写入_output,否则跳过</span><br> <span class="hljs-keyword">if</span> (dirty_check[write_p]) {<br> <span class="hljs-type">size_t</span> len = <span class="hljs-number">0</span>;<br> <span class="hljs-type">size_t</span> output_remaining = _output.<span class="hljs-built_in">remaining_capacity</span>();<br> <span class="hljs-keyword">while</span> (dirty_check[write_p + len] && len < output_remaining) {<br> len++;<br> }<br> _output.<span class="hljs-built_in">write</span>(cache.<span class="hljs-built_in">substr</span>(write_p, len));<br> write_p += len;<br> }<br><br> <span class="hljs-comment">// 写入位和EOF位相同,代表写入结束</span><br> <span class="hljs-keyword">if</span> (write_p == end_p) {<br> _output.<span class="hljs-built_in">end_input</span>();<br> }<br>}<br></code></pre></td></tr></table></figure><p>对于没有统计的字符数量,直接使用一个循环进行统计即可</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 返回缓冲区内还没有处理的内容</span><br><span class="hljs-function"><span class="hljs-type">size_t</span> <span class="hljs-title">StreamReassembler::unassembled_bytes</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{<br> <span class="hljs-type">size_t</span> n = write_p;<br> <span class="hljs-comment">// 检查缓存区有多少字符</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = write_p; n != cache.<span class="hljs-built_in">length</span>() && <span class="hljs-keyword">not</span> dirty_check[i]; i++) {<br> n++;<br> }<br> <span class="hljs-keyword">return</span> cache.<span class="hljs-built_in">length</span>() - n;<br>}<br></code></pre></td></tr></table></figure><p>对于判断缓冲区是否使用完毕则是</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// 当不再写入新的TCP段并且已有的字段全部排序结束的时候缓冲区不再需要排序</span><br><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">StreamReassembler::empty</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> _output.<span class="hljs-built_in">eof</span>() && <span class="hljs-keyword">not</span> <span class="hljs-built_in">unassembled_bytes</span>(); }<br></code></pre></td></tr></table></figure><h3 id="测试案例的补充"><a class="markdownIt-Anchor" href="#测试案例的补充"></a> 测试案例的补充</h3><p>使用上面这种写法的话虽然可以达到<code>100% tests passed</code>,并且时间也都能控制在<code>0.5s</code>以内,但是在复习了真实情况下的重组过程发现这个思路存在一些BUG是测试案例没有检测出来的。</p><h4 id="不会抛弃本来应该抛弃的数据同时产生错误的eof位置标记"><a class="markdownIt-Anchor" href="#不会抛弃本来应该抛弃的数据同时产生错误的eof位置标记"></a> 不会抛弃本来应该抛弃的数据,同时产生错误的EOF位置标记</h4><p>以如下的<code>test</code>为例</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs cpp">{<br> ReassemblerTestHarness test{<span class="hljs-number">6</span>};<br> test.<span class="hljs-built_in">execute</span>(SubmitSegment{<span class="hljs-string">"defg"</span>, <span class="hljs-number">3</span>});<br> test.<span class="hljs-built_in">execute</span>(<span class="hljs-built_in">BytesAssembled</span>(<span class="hljs-number">0</span>));<br> test.<span class="hljs-built_in">execute</span>(SubmitSegment{<span class="hljs-string">"abc"</span>, <span class="hljs-number">0</span>});<br> test.<span class="hljs-built_in">execute</span>(<span class="hljs-built_in">BytesAvailable</span>(<span class="hljs-string">"abcdef"</span>));<br> test.<span class="hljs-built_in">execute</span>(<span class="hljs-built_in">BytesAssembled</span>(<span class="hljs-number">6</span>));<br> test.<span class="hljs-built_in">execute</span>(SubmitSegment{<span class="hljs-string">"kmg"</span>, <span class="hljs-number">7</span>});<br> test.<span class="hljs-built_in">execute</span>(<span class="hljs-built_in">BytesAvailable</span>(<span class="hljs-string">""</span>));<br>}<br></code></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></pre></td><td class="code"><pre><code class="hljs log">Test Failure on expectation:<br> Expectation: stream_out().buffer_size() returned 0, and stream_out().read(0) returned the string ""<br><br>Failure message:<br> The reassembler was expected to have `0` bytes available, but there were `4`<br><br>List of steps that executed successfully:<br> Initialized (capacity = 6)<br> Action: substring submitted with data "defg", index `3`, eof `0`<br> Expectation: net bytes assembled = 0<br> Action: substring submitted with data "abc", index `0`, eof `0`<br> Expectation: stream_out().buffer_size() returned 6, and stream_out().read(6) returned the string "abcdef"<br> Expectation: net bytes assembled = 6<br> Action: substring submitted with data "kmg", index `7`, eof `0`<br><br>Exception: The reassembler was expected to have `0` bytes available, but there were `4`<br></code></pre></td></tr></table></figure><p>可以发现,本来在传入第一个<code>defg</code>的时候,字符<code>g</code>应该因为超出<code>capacity</code>而被抛弃,但是是将并没有,导致<code>g</code>停留在了<code>index</code>为<code>6</code>的位置上。读取了<code>_output</code>的所有内容之后,<code>_output</code>的窗口应该是从<code>index</code>为<code>6</code>的位置上开始准备写入,但是由于这个位置上的<code>g</code>在上一个窗口期中并没有被抛弃,结果导致了<code>index</code>为<code>7</code>的<code>kmg</code>写入的时候,连带前面存在的<code>g</code>一起将<code>gkmg</code>写入了<code>_output</code>的窗口当中,从而出现了以下报错。</p><blockquote><p>The reassembler was expected to have <code>0</code> bytes available, but there were <code>4</code></p></blockquote><p>同时对于EOF的位置判断也有类似的BUG,测试样例如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs cpp">{<br> ReassemblerTestHarness test{<span class="hljs-number">6</span>};<br> test.<span class="hljs-built_in">execute</span>(SubmitSegment{<span class="hljs-string">"defx"</span>, <span class="hljs-number">3</span>}.<span class="hljs-built_in">with_eof</span>(<span class="hljs-literal">true</span>));<br> test.<span class="hljs-built_in">execute</span>(<span class="hljs-built_in">BytesAssembled</span>(<span class="hljs-number">0</span>));<br> test.<span class="hljs-built_in">execute</span>(SubmitSegment{<span class="hljs-string">"abc"</span>, <span class="hljs-number">0</span>});<br> test.<span class="hljs-built_in">execute</span>(<span class="hljs-built_in">BytesAvailable</span>(<span class="hljs-string">"abcdef"</span>));<br> test.<span class="hljs-built_in">execute</span>(<span class="hljs-built_in">BytesAssembled</span>(<span class="hljs-number">6</span>));<br> test.<span class="hljs-built_in">execute</span>(SubmitSegment{<span class="hljs-string">"g"</span>, <span class="hljs-number">6</span>});<br> test.<span class="hljs-built_in">execute</span>(<span class="hljs-built_in">BytesAvailable</span>(<span class="hljs-string">"g"</span>));<br> test.<span class="hljs-built_in">execute</span>(NotAtEof{});<br>}<br></code></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></pre></td><td class="code"><pre><code class="hljs log">Test Failure on expectation:<br> Expectation: not at EOF<br><br>Failure message:<br> The reassembler was expected to **not** be at EOF, but was<br><br>List of steps that executed successfully:<br> Initialized (capacity = 6)<br> Action: substring submitted with data "defx", index `3`, eof `1`<br> Expectation: net bytes assembled = 0<br> Action: substring submitted with data "abc", index `0`, eof `0`<br> Expectation: stream_out().buffer_size() returned 6, and stream_out().read(6) returned the string "abcdef"<br> Expectation: net bytes assembled = 6<br> Action: substring submitted with data "g", index `6`, eof `0`<br> Expectation: stream_out().buffer_size() returned 1, and stream_out().read(1) returned the string "g"<br><br>Exception: The reassembler was expected to **not** be at EOF, but was<br></code></pre></td></tr></table></figure><p>本来在第一个操作的时候作为<code>eof</code>的<code>x</code>应该是被抛弃掉并不读取的,但是在最后这个位置的<code>eof_p</code>还是触发了<code>EOF</code>判断,导致产生了不应该出现的<code>EOF</code>。</p><h2 id="修正方案"><a class="markdownIt-Anchor" href="#修正方案"></a> 修正方案</h2><p>本质的问题就是没有丢弃掉<code>unacceptable</code>的字节,这里采取了一个比较省事但是很不优雅的操作,我是选择在最后扩容后重新再用<code>resize()</code>函数将不需要的那部分丢弃掉,来达到限制容量的目的,最后修正完毕的实现如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">StreamReassembler::push_substring</span><span class="hljs-params">(<span class="hljs-type">const</span> string &data, <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> index, <span class="hljs-type">const</span> <span class="hljs-type">bool</span> eof)</span> </span>{<br> <span class="hljs-type">bool</span> eof_flag = <span class="hljs-literal">false</span>;<br> <span class="hljs-type">size_t</span> expand_size = index + data.<span class="hljs-built_in">length</span>();<br><br> <span class="hljs-comment">// 短路错误index</span><br> <span class="hljs-keyword">if</span> (index > write_p + _output.<span class="hljs-built_in">remaining_capacity</span>()) {<br> <span class="hljs-keyword">return</span>;<br> }<br><br> <span class="hljs-comment">// 取 index + data.length() 和</span><br> <span class="hljs-comment">// write_p + _output.remaining_capacity() 中更小的那个作为扩容后的大小</span><br> <span class="hljs-keyword">if</span> (index + data.<span class="hljs-built_in">length</span>() <= write_p + _output.<span class="hljs-built_in">remaining_capacity</span>()) {<br> <span class="hljs-comment">// 用于判断EOF是否是在capacity当中的有效字符</span><br> eof_flag = <span class="hljs-literal">true</span>;<br> expand_size = index + data.<span class="hljs-built_in">length</span>();<br> } <span class="hljs-keyword">else</span> {<br> expand_size = write_p + _output.<span class="hljs-built_in">remaining_capacity</span>();<br> }<br><br> <span class="hljs-comment">// 记录EOF的位置</span><br> <span class="hljs-keyword">if</span> (eof && eof_flag) {<br> end_p = expand_size;<br> }<br><br> <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> cache_raw_length = cache.<span class="hljs-built_in">length</span>();<br><br> <span class="hljs-comment">// 先扩大一次容量,用于写入多余的内容</span><br> <span class="hljs-keyword">if</span> (expand_size > cache_raw_length) {<br> cache.<span class="hljs-built_in">resize</span>(expand_size);<br> dirty_check.<span class="hljs-built_in">resize</span>(expand_size);<br> }<br><br> <span class="hljs-comment">// 将要排序的内容先写入cache当中</span><br> cache.<span class="hljs-built_in">replace</span>(index, data.<span class="hljs-built_in">length</span>(), data);<br> dirty_check.<span class="hljs-built_in">replace</span>(index, data.<span class="hljs-built_in">length</span>(), data.<span class="hljs-built_in">length</span>(), <span class="hljs-string">'1'</span>);<br><br> <span class="hljs-comment">// 缩回原来的大小,将缓冲区外多余的内容丢弃</span><br> <span class="hljs-keyword">if</span> (expand_size > cache_raw_length) {<br> cache.<span class="hljs-built_in">resize</span>(expand_size);<br> dirty_check.<span class="hljs-built_in">resize</span>(expand_size);<br> }<br><br> <span class="hljs-comment">// 检查写入位上是否有字符,有字符则通过滑动len来写入_output,否则跳过</span><br> <span class="hljs-keyword">if</span> (dirty_check[write_p]) {<br> <span class="hljs-type">size_t</span> len = <span class="hljs-number">0</span>;<br> <span class="hljs-type">size_t</span> output_remaining = _output.<span class="hljs-built_in">remaining_capacity</span>();<br> <span class="hljs-keyword">while</span> (dirty_check[write_p + len] && len < output_remaining) {<br> len++;<br> }<br> _output.<span class="hljs-built_in">write</span>(cache.<span class="hljs-built_in">substr</span>(write_p, len));<br> write_p += len;<br> }<br><br> <span class="hljs-comment">// 写入位和EOF位相同,代表写入结束</span><br> <span class="hljs-keyword">if</span> (write_p == end_p) {<br> _output.<span class="hljs-built_in">end_input</span>();<br> }<br>}<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>cs144</tag>
<tag>network</tag>
</tags>
</entry>
<entry>
<title>CS144-Lab0 计算机网络:流的输入和读出</title>
<link href="/p/2ca0860a.html"/>
<url>/p/2ca0860a.html</url>
<content type="html"><![CDATA[<h2 id="热身"><a class="markdownIt-Anchor" href="#热身"></a> 热身</h2><p><code>lab0</code>前后分为两个较为简单的小任务,第一个任务是写一个类似<code>telnet</code>中通信的<code>webget</code>小应用,第二个任务是实现一个简单的<code>ByteStream</code>的类,只需要在单线程的情况下能正常运行即可</p><h3 id="任务一"><a class="markdownIt-Anchor" href="#任务一"></a> 任务一</h3><p>第一个任务的参考主要是从项目文件本身的<code>doctests</code>开始着手,其中在提示中已经说了我们将会使用到<code>TCPSocket</code>和<code>Address</code>,在对应的<code>doctests/socket_example_2.cc</code>和<code>doctests/address_example_1.cc</code>中,我们可以得到对于他们的使用例子,只需要创建一个以目标<code>Address</code>初始化并连接的<code>TCPSocket</code>,然后以这个<code>socket</code>向目标服务器发送类似<code>telnet</code>的请求即可获得我们需要的内容</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs cpp">TCPSocket socket;<br>socket.<span class="hljs-built_in">connect</span>(<span class="hljs-built_in">Address</span>(host, <span class="hljs-string">"http"</span>));<br>socket.<span class="hljs-built_in">write</span>(<span class="hljs-string">"GET "</span> + path + <span class="hljs-string">" HTTP/1.1\r\n"</span>);<br>socket.<span class="hljs-built_in">write</span>(<span class="hljs-string">"Host: "</span> + host + <span class="hljs-string">"\r\n"</span>);<br>socket.<span class="hljs-built_in">write</span>(<span class="hljs-string">"Connection: close\r\n\r\n"</span>);<br></code></pre></td></tr></table></figure><p>由于最后在输入完<code>Connection: close</code>之后,我们本来也要输入一个回车将请求发送,因此在这里需要两个换行符</p><p>在处理<code>socket.read()</code>的时候,起初没有仔细考虑pdf中提到的<code>a single call to read is not enough</code>的具体含义,以为是首先会接受所有的文本信息,然后对于结果需要将最后的<code>EOF</code>也打印出来,所以第一次写的时候只是简单的调用了两次<code>read()</code></p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cpp">cout << socket.<span class="hljs-built_in">read</span>() << socket.<span class="hljs-built_in">read</span>();<br></code></pre></td></tr></table></figure><p>然而在<code>make check_webget</code>的时候并没有通过,为了找到问题所在,首先找到<code>check_webget</code>的脚本,发现测试的内容为对<code>cs144.keithw.org</code>下的接口<code>/nph-hasher/xyzzy</code>发送请求,并获取最后一行的内容,而这行内容应该是一串正确的HASH。但是在这个时候尝试以上文的方式运行<code>webget</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><code class="hljs plain">HTTP/1.1 200 OK<br>Content-type: text/plain<br></code></pre></td></tr></table></figure><p>但是通过<code>telnet</code>的情况下,正常的输入应该是以下的内容</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><code class="hljs plain">HTTP/1.1 200 OK<br>Content-type: text/plain<br><br>7SmXqWkrLKzVBCEalbSPqBcvs11Pw263K7x4Wv3JckI<br></code></pre></td></tr></table></figure><p>这个时候就懂了多次调用<code>read()</code>的含义直到遇到<code>eof</code>的含义应该是一直读取到所有缓冲区内的内容都被读取完毕,将代码修改如下就可以通过测试了</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">get_URL</span><span class="hljs-params">(<span class="hljs-type">const</span> string &host, <span class="hljs-type">const</span> string &path)</span> </span>{<br> TCPSocket socket;<br> socket.<span class="hljs-built_in">connect</span>(<span class="hljs-built_in">Address</span>(host, <span class="hljs-string">"http"</span>));<br> socket.<span class="hljs-built_in">write</span>(<span class="hljs-string">"GET "</span> + path + <span class="hljs-string">" HTTP/1.1\r\n"</span>);<br> socket.<span class="hljs-built_in">write</span>(<span class="hljs-string">"Host: "</span> + host + <span class="hljs-string">"\r\n"</span>);<br> socket.<span class="hljs-built_in">write</span>(<span class="hljs-string">"Connection: close\r\n\r\n"</span>);<br> <span class="hljs-keyword">while</span> (!socket.<span class="hljs-built_in">eof</span>()) {<br> cout << socket.<span class="hljs-built_in">read</span>();<br> }<br> socket.<span class="hljs-built_in">close</span>();<br>}<br></code></pre></td></tr></table></figure><h3 id="任务二"><a class="markdownIt-Anchor" href="#任务二"></a> 任务二</h3><p>第二个任务主要是要我们自己根据头文件的内容来实现一个简单的<code>ByteStream</code>,并且只需要考虑单线程的情况,不用考虑并发等情况。</p><p>最后完成的答案先直接贴上来,这块难度也不会很大,在写的时候先大致按要求写出一个逻辑,即使不是很清楚具体实现对不对也问题不大,只需要通过调试逐步修改即可</p><p>首先添加的成员变量如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">class</span> <span class="hljs-title class_">ByteStream</span> {<br> <span class="hljs-keyword">private</span>:<br> <span class="hljs-comment">// buffer capacity</span><br> <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> buffer_max_size;<br><br> <span class="hljs-comment">// buffer string</span><br> std::string buffer;<br><br> <span class="hljs-comment">// input ending flag</span><br> <span class="hljs-type">bool</span> is_input_end = <span class="hljs-literal">false</span>;<br><br> <span class="hljs-comment">// counter</span><br> <span class="hljs-type">size_t</span> write_count, read_count;<br><br> <span class="hljs-comment">// Hint: This doesn't need to be a sophisticated data structure at</span><br> <span class="hljs-comment">// all, but if any of your tests are taking longer than a second,</span><br> <span class="hljs-comment">// that's a sign that you probably want to keep exploring</span><br> <span class="hljs-comment">// different approaches.</span><br><br> <span class="hljs-type">bool</span> _error{}; <span class="hljs-comment">//!< Flag indicating that the stream suffered an error.</span><br><br> <span class="hljs-keyword">public</span>:<br> ...<br></code></pre></td></tr></table></figure><p>最后接口的实现如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><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></pre></td><td class="code"><pre><code class="hljs cpp">ByteStream::<span class="hljs-built_in">ByteStream</span>(<span class="hljs-type">const</span> <span class="hljs-type">size_t</span> capacity) : <span class="hljs-built_in">buffer_max_size</span>(capacity), <span class="hljs-built_in">buffer</span>(), <span class="hljs-built_in">write_count</span>(<span class="hljs-number">0</span>), <span class="hljs-built_in">read_count</span>(<span class="hljs-number">0</span>) {}<br><br><span class="hljs-function"><span class="hljs-type">size_t</span> <span class="hljs-title">ByteStream::write</span><span class="hljs-params">(<span class="hljs-type">const</span> string &data)</span> </span>{<br> <span class="hljs-comment">// 不需要在这里判断input_ended,因为写入过程都是单线程,如果要ended肯定是在write之后进行的</span><br> <span class="hljs-type">size_t</span> cnt = <span class="hljs-built_in">min</span>(<span class="hljs-built_in">remaining_capacity</span>(), data.<span class="hljs-built_in">length</span>());<br> buffer += data.<span class="hljs-built_in">substr</span>(<span class="hljs-number">0</span>, cnt);<br> write_count += cnt;<br> <span class="hljs-keyword">return</span> cnt;<br>}<br><br><span class="hljs-comment">//! \param[in] len bytes will be copied from the output side of the buffer</span><br><span class="hljs-function">string <span class="hljs-title">ByteStream::peek_output</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">size_t</span> len)</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> buffer.<span class="hljs-built_in">substr</span>(<span class="hljs-number">0</span>, len); }<br><br><span class="hljs-comment">//! \param[in] len bytes will be removed from the output side of the buffer</span><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ByteStream::pop_output</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">size_t</span> len)</span> </span>{<br> buffer.<span class="hljs-built_in">erase</span>(<span class="hljs-number">0</span>, len);<br> read_count += len;<br>}<br><br><span class="hljs-comment">//! Read (i.e., copy and then pop) the next "len" bytes of the stream</span><br><span class="hljs-comment">//! \param[in] len bytes will be popped and returned</span><br><span class="hljs-comment">//! \returns a string</span><br><span class="hljs-function">std::string <span class="hljs-title">ByteStream::read</span><span class="hljs-params">(<span class="hljs-type">const</span> <span class="hljs-type">size_t</span> len)</span> </span>{<br> string output = buffer.<span class="hljs-built_in">substr</span>(<span class="hljs-number">0</span>, len);<br> <span class="hljs-built_in">pop_output</span>(len);<br> <span class="hljs-comment">// 在pop_output的时候会计算读取,这里不需要再+=len</span><br> <span class="hljs-keyword">return</span> output;<br>}<br><br><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">ByteStream::end_input</span><span class="hljs-params">()</span> </span>{ is_input_end = <span class="hljs-literal">true</span>; }<br><br><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">ByteStream::input_ended</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> is_input_end; }<br><br><span class="hljs-function"><span class="hljs-type">size_t</span> <span class="hljs-title">ByteStream::buffer_size</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> buffer.<span class="hljs-built_in">length</span>(); }<br><br><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">ByteStream::buffer_empty</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> buffer.<span class="hljs-built_in">empty</span>(); }<br><br><span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">ByteStream::eof</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> <span class="hljs-built_in">input_ended</span>() && buffer.<span class="hljs-built_in">empty</span>(); }<br><br><span class="hljs-function"><span class="hljs-type">size_t</span> <span class="hljs-title">ByteStream::bytes_written</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> write_count; }<br><br><span class="hljs-function"><span class="hljs-type">size_t</span> <span class="hljs-title">ByteStream::bytes_read</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> read_count; }<br><br><span class="hljs-function"><span class="hljs-type">size_t</span> <span class="hljs-title">ByteStream::remaining_capacity</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> buffer_max_size - buffer.<span class="hljs-built_in">length</span>(); }<br></code></pre></td></tr></table></figure><h4 id="调试"><a class="markdownIt-Anchor" href="#调试"></a> 调试</h4><p>接口的逻辑实现都不难,不过写这个<code>lab</code>的时候的第一次在<code>vscode</code>的环境下使用<code>cmake</code>来进行调试,在这里简单记录一下调试的步骤和需求。</p><p><strong>相关code插件</strong>: CMake, CMake Tools, C/C++(Cpptools)</p><p>在这里以<code>write()</code>函数中缺少了<code>write_count += cnt</code>这一行为例,使用<code>make check_lab0</code>可以发现在最后有报错</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></pre></td><td class="code"><pre><code class="hljs log">56% tests passed, 4 tests failed out of 9<br><br>Total Test time (real) = 1.49 sec<br><br>The following tests FAILED:<br> 27 - t_byte_stream_one_write (Failed)<br> 28 - t_byte_stream_two_writes (Failed)<br> 29 - t_byte_stream_capacity (Failed)<br> 30 - t_byte_stream_many_writes (Failed)<br>Errors while running CTest<br>make[3]: *** [CMakeFiles/check_lab0.dir/build.make:71: CMakeFiles/check_lab0] Error 8<br>make[2]: *** [CMakeFiles/Makefile2:228: CMakeFiles/check_lab0.dir/all] Error 2<br>make[1]: *** [CMakeFiles/Makefile2:235: CMakeFiles/check_lab0.dir/rule] Error 2<br>make: *** [Makefile:160: check_lab0] Error 2<br></code></pre></td></tr></table></figure><p>此时可以将注意力先集中在最上面的<code>t_byte_stream_one_write</code>上,<code>cmake</code>中将<code>byte_stream_one_write</code>作为<code>target</code>编译并运行,可以得到以下报错</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></pre></td><td class="code"><pre><code class="hljs log">Test Failure on expectation:<br> Expectation: bytes_written: 3<br><br>Failure message:<br> The ByteStream should have had bytes_written equal to 3 but instead it was 0<br><br>List of steps that executed successfully:<br> Initialized with (capacity=15)<br> Action: write "cat" to the stream<br> Expectation: input_ended: 0<br> Expectation: buffer_empty: 0<br> Expectation: eof: 0<br> Expectation: bytes_read: 0<br><br>Exception: The test "write-end-pop" failed<br></code></pre></td></tr></table></figure><p>从这里可以知道是测试<code>"write-end-pop"</code>中的第五个执行中,<code>bytes_written</code>并没有返回预期希望的数字<code>3</code>。因此只需要在<code>tests/byte_stream_one_write.cc</code>内"write-end-pop"的<code>test.execute(BytesWritten{3});</code>的位置打上断点,然后直接使用<code>vscode</code>下方栏中<code>cmake</code>的<code>debug</code>图标(需要安装C/C++插件才可以使用快速调试,具体参考<a href="https://github.com/microsoft/vscode-cmake-tools/blob/f85174dac4562ea10da73cceee1d84394c0a808c/docs/debug-launch.md#quick-debugging">这里</a>)就可以逐步找到自己逻辑中出错的地方并修改即可。</p><p>热身的两个<code>lab</code>提供的成就感很足,希望自己能尽快完成下一个<code>lab</code>。</p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>cs144</tag>
<tag>network</tag>
</tags>
</entry>
<entry>
<title>ArchWSL安装及基础配置</title>
<link href="/p/cdfd3649.html"/>
<url>/p/cdfd3649.html</url>
<content type="html"><![CDATA[<h2 id="准备工作"><a class="markdownIt-Anchor" href="#准备工作"></a> 准备工作</h2><p>在<code>Windows</code>上安装<code>ArchWSL</code>直接使用<code>scoop</code>来安装是比较便捷的一种方案,具体如何在<code>Windows</code>上配置·scoop`可以参考这篇教程:</p><ul><li><a href="https://halc.top/p/f15c20eb.html">Windows上通过Scoop管理和安装软件</a></li></ul><p>在配置好了<code>scoop</code>以后,首先通过以下指令安装<code>ArchWSL</code></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">scoop install archwsl<br></code></pre></td></tr></table></figure><p>只要按正常流程,按<code>win+s</code>,通过Windows搜索找“Turn Windows features on or off”或“启用或关闭Windows功能”,然后在里面将 <strong>Virtual Machine Platform</strong> 和 <strong>Windows Subsystem for Linux</strong> 勾选上,重启电脑即可。</p><p>重启电脑之后只需要在命令行中输入<code>arch</code>即可启动,如果出现报错或无法使用 <code>WSL2</code> 的情况可以通过搜索引擎或在这里<a href="https://docs.microsoft.com/zh-cn/windows/wsl/install-manual#step-4---download-the-linux-kernel-update-package">下载Linux内核更新包</a>来解决</p><h2 id="配置镜像源并创建非root用户"><a class="markdownIt-Anchor" href="#配置镜像源并创建非root用户"></a> 配置镜像源并创建非root用户</h2><h3 id="启用mirrorlist包含镜像"><a class="markdownIt-Anchor" href="#启用mirrorlist包含镜像"></a> 启用mirrorlist包含镜像</h3><p>在<code>/etc/pacman.d/mirrorlist</code>内已经有<code>Arch</code>预置好的部分国内镜像源,我们只需要将我们对应需要的镜像前面的注释取消即可使用。</p><p>或者也可以通过下面这个脚本来一键启用所有<code>China</code>部分的镜像源</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">sed -E <span class="hljs-string">'/China/,/##/s/^#S(.)/S\1/g'</span> /etc/pacman.d/mirrorlist~ > /etc/pacman.d/mirrorlist<br></code></pre></td></tr></table></figure><h3 id="添加archlinuxcn源"><a class="markdownIt-Anchor" href="#添加archlinuxcn源"></a> 添加archlinuxcn源</h3><p>通过以下指令将<code>archlinuxcn</code>相关源直接写入<code>/etc/pacman.conf</code>当中</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">cat</span> >> /etc/pacman.conf << <span class="hljs-string">EOF</span><br><span class="hljs-string"></span><br><span class="hljs-string"></span><br><span class="hljs-string"># 国内archlinuxcn镜像源</span><br><span class="hljs-string">[archlinuxcn]</span><br><span class="hljs-string">Server = https://mirrors.aliyun.com/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://repo.archlinuxcn.org/\$arch</span><br><span class="hljs-string">Server = https://mirrors.bfsu.edu.cn/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.cloud.tencent.com/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.163.com/archlinux-cn/\$arch</span><br><span class="hljs-string">Server = https://repo.huaweicloud.com/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.zju.edu.cn/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.cqupt.edu.cn/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.sjtug.sjtu.edu.cn/archlinux-cn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/\$arch</span><br><span class="hljs-string">EOF</span><br></code></pre></td></tr></table></figure><h3 id="安装yay和基础环境"><a class="markdownIt-Anchor" href="#安装yay和基础环境"></a> 安装yay和基础环境</h3><p>首先通过以下指令,来更新软件源、安装<code>archlinuxcn</code>证书、<code>yay</code>和部分基础工具(其中yadm是一个用来备份<code>dotfiles</code>的工具,用于恢复自己常用的<code>Linux</code>环境下的自定义文件)</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash">pacman -Syyu --noconfirm ;<br>pacman -S archlinuxcn-keyring --noconfirm && pacman -S yay wget curl zsh yadm --noconfirm <br></code></pre></td></tr></table></figure><h3 id="创建非root用户"><a class="markdownIt-Anchor" href="#创建非root用户"></a> 创建非root用户</h3><ol><li><p>创建用户 <strong>(注意替换下文的<code>用户名</code>)</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">useradd -m -G wheel -s /bin/zsh 用户名<br></code></pre></td></tr></table></figure></li><li><p>将<code>wheel</code>组内的成员给予<code>sudo</code>的权限</p><p>如果希望安全考虑,在<code>sudo</code>之前要输入密码的话,可以输入下面的指令来配置<code>visudo</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">'%wheel ALL=(ALL:ALL) ALL'</span> | sudo EDITOR=<span class="hljs-string">'tee -a'</span> visudo<br></code></pre></td></tr></table></figure><p>如果偷懒,不希望每次都输入密码的话可以用下面的指令来配置<code>visudo</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">'%wheel ALL=(ALL:ALL) NOPASSWD: ALL'</span> | sudo EDITOR=<span class="hljs-string">'tee -a'</span> visudo<br></code></pre></td></tr></table></figure><blockquote><p>2.5. 以上所有操作全部可以自动完成,只需要将以下脚本内的用户名替换为自己的用户名即可(默认sudo不需要密码)</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs bash">sed -E <span class="hljs-string">'/China/,/##/s/^#S(.)/S\1/g'</span> /etc/pacman.d/mirrorlist~ > /etc/pacman.d/mirrorlist<br><span class="hljs-built_in">cat</span> >> /etc/pacman.conf << <span class="hljs-string">EOF</span><br><span class="hljs-string"></span><br><span class="hljs-string"></span><br><span class="hljs-string"># 国内archlinuxcn镜像源</span><br><span class="hljs-string">[archlinuxcn]</span><br><span class="hljs-string">Server = https://mirrors.aliyun.com/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://repo.archlinuxcn.org/\$arch</span><br><span class="hljs-string">Server = https://mirrors.bfsu.edu.cn/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.cloud.tencent.com/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.163.com/archlinux-cn/\$arch</span><br><span class="hljs-string">Server = https://repo.huaweicloud.com/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.zju.edu.cn/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.cqupt.edu.cn/archlinuxcn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.sjtug.sjtu.edu.cn/archlinux-cn/\$arch</span><br><span class="hljs-string">Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/\$arch</span><br><span class="hljs-string">EOF</span><br>pacman -Syyu --noconfirm ;<br>pacman -S archlinuxcn-keyring --noconfirm && pacman -S yay wget curl zsh yadm --noconfirm <br><span class="hljs-built_in">echo</span> <span class="hljs-string">'%wheel ALL=(ALL:ALL) NOPASSWD: ALL'</span> | sudo EDITOR=<span class="hljs-string">'tee -a'</span> visudo<br>useradd -m -G wheel -s /bin/zsh 用户名<br></code></pre></td></tr></table></figure></blockquote></li><li><p>给root用户和自定义用户设置密码(自行操作)</p><p>修改root用户密码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">passwd root<br></code></pre></td></tr></table></figure><p>修改自定义用户密码</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">passwd 用户名<br></code></pre></td></tr></table></figure></li><li><p>(可选)切换到自定义用户下并配置<code>oh-my-zsh</code></p><p>通过以下指令切换用户</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">su 用户名<br></code></pre></td></tr></table></figure><p>配置<code>zsh</code>为<code>oh-my-zsh</code>可以参考这篇教程(Ubuntu配置和Arch大同小异,可以选择性参考)</p><p><a href="https://halc.top/p/80f884dc.html">Ubuntu下安装Oh My Zsh引导</a></p></li></ol><h2 id="设置自定义用户和archwsl为默认"><a class="markdownIt-Anchor" href="#设置自定义用户和archwsl为默认"></a> 设置自定义用户和ArchWSL为默认</h2><p>如果希望设置<code>ArchWSL</code>为<code>WSL</code>的默认发行版,并将刚刚自己创建的用户作为默认用户的话只需要分别在<code>Powershell</code>下执行这两条指令</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs powershell">wsl <span class="hljs-literal">-s</span> Arch<br></code></pre></td></tr></table></figure><figure class="highlight ps"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ps">Arch config <span class="hljs-literal">--default-user</span> 用户名<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>安装引导</category>
</categories>
<tags>
<tag>WSL</tag>
<tag>Arch</tag>
</tags>
</entry>
<entry>
<title>Shell:管道符与重定向</title>
<link href="/p/db6cc46f.html"/>
<url>/p/db6cc46f.html</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>到目前位置自己还没专门花时间研究过<code>Linux</code>上那些日日都在用的工具(如<code>Shell</code>和<code>Vim</code>)他们本来的用法和含义,本来觉得没必要,不过在看了<code>missing-semester</code>之后顿时感觉效率提高了不少。因此做一个笔记,把一些很实用但是自己并不会去关注的简单用法给记录一下。</p><h2 id="参考文章"><a class="markdownIt-Anchor" href="#参考文章"></a> 参考文章</h2><ul><li><a href="https://blog.51cto.com/yuyucat/1651914">Linux shell管道与重定向实例分析</a></li><li><a href="https://missing-semester-cn.github.io/2020/shell-tools/">Shell预览</a></li><li><a href="https://stackoverflow.com/a/12168594">How does “<<” operator work in linux shell?</a></li><li><a href="https://unix.stackexchange.com/a/14249">Precedence of stdin and stdout redirection in Bash</a></li><li><a href="https://stackoverflow.com/a/29783586">Redirection and pipe behavior in bash vs. zsh</a></li></ul><h2 id="shell"><a class="markdownIt-Anchor" href="#shell"></a> Shell</h2><h3 id="prompt"><a class="markdownIt-Anchor" href="#prompt"></a> prompt</h3><p>在使用最基础的<code>bash</code>作为命令行的时候,常常能发现用户后面有的时候是<code>$</code>二有的时候是<code>#</code></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><code class="hljs shell">[halc@Zephyrus blog]$ su<br>[root@Zephyrus blog]#<br></code></pre></td></tr></table></figure><p>这里<code>$</code>代表了当前用户为普通用户,而<code>#</code>的含义则代表当前是在<code>root</code>用户下</p><h3 id="重定向"><a class="markdownIt-Anchor" href="#重定向"></a> 重定向</h3><h4 id="输出重定向"><a class="markdownIt-Anchor" href="#输出重定向"></a> 输出重定向</h4><p>在<code>shell</code>程序中,程序的执行主要分为 <strong>输入流</strong> 和 <strong>输出流</strong> 两种不同的流,程序会从输入流中读取信息,然后在通过处理之后打印到输出流当中。</p><p>以<code>cat</code>为例,其作用为将一个文件的内容打印到输出流当中,通过使用 <code>> file</code> 和 <code>< file</code> 来对流进行重定向,可以覆写或者创建对应内容的文件</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">将我存于github的pubkey打印输出成id_rsa.pub文件</span><br>curl https://github.com/HalcyonAzure.keys > id_rsa.pub<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">将id_rsa.pub的内容重定向 **追加** 到authorized_keys当中</span><br>cat id_rsa.pub >> authorized_keys<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">将id_rsa.pub的内容先重定向给<span class="hljs-built_in">cat</span>,再由<span class="hljs-built_in">cat</span>输出到pubkey当中</span><br>cat < id_rsa.pub > pubkey<br></code></pre></td></tr></table></figure><blockquote><p>拓展补充 1:所有的Linux进程都包含3个文件描述符,分别是 <strong>标准输入</strong>,<strong>标准输出</strong> 和 <strong>错误输出</strong>。<br />标准输入(stdin):对应的文件标识符为0,使用<code><</code>或<code><<</code>来操作<br />标准输出(stdout):对应的文件标识符为1,使用<code>></code>或<code>>></code>来操作<br />错误输出(stderr): 对应的文件标识符为2,使用<code>2></code>或<code>2>></code>来操作<br />默认情况下,标准输出和错误输出都默认为使用者的屏幕,通过使用输出重定向可以做到控制日志的效果</p><p>补充拓展 2:<code>>/dev/null 2>&1</code>和<code>2>&1 >/dev/null</code>的区别</p><ul><li>对于<code>>/dev/null 2>&1</code>是指先将标准输出指向黑洞设备,然后再将错误输出指向标准输出的指向内容(此时是黑洞),因此标准输出和错误输出都将不输出</li><li>对于<code>2>&1 >/dev/null</code>是先将错误输出指向标准输出(此时为屏幕),然后将标准输出指向黑洞,因此此时错误输出将打印到屏幕,而标准输出将不输出</li></ul></blockquote><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><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">运行example.sh,并将标准输出重定向至stdout.log,将错误输出重定向至stderr.log</span><br>bash example.sh 1>stdout.log 2>stderr.log<br></code></pre></td></tr></table></figure><p>以一个简单的test.cpp程序为例</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></pre></td><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-keyword">include</span><span class="hljs-string"><iostream></span></span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> std;<br><br><span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span> </span>{<br> cout << <span class="hljs-string">"This is stand output"</span> << endl;<br> cerr << <span class="hljs-string">"This is error output"</span> << endl;<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><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><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><code class="hljs shell">[halc@Zephyrus ~]$ g++ test.cpp -o test<br>[halc@Zephyrus ~]$ ./test 1>stand.log 2>err.log<br>[halc@Zephyrus ~]$ rg output *.log<br>stand.log<br>1:This is stand output<br><br>err.log<br>1:This is error output<br></code></pre></td></tr></table></figure><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><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">$-指关闭对应的文件操作符</span><br>[halc@Zephyrus ~]$ ./test 1>$- 2>$-<br><span class="hljs-meta prompt_"># </span><span class="language-bash">/dev/null为linux内的黑洞设备</span><br>[halc@Zephyrus ~]$ ./test 1>/dev/null 2>/dev/null<br></code></pre></td></tr></table></figure><blockquote><ul><li>对于<code>></code>操作符,首先会判断右侧的文件是否存在,如果存在就删除再创建,如果不存在则直接创建,并且右侧的文件一定会置空。</li><li>一条命令执行前会检查0,1,2三个I/O设备是否正常,如果异常则不会进行命令执行</li></ul></blockquote><h4 id="输入重定向"><a class="markdownIt-Anchor" href="#输入重定向"></a> 输入重定向</h4><p>如果想要将内容输出重定向到某个文件,以<code>cat</code>举例有两种不同的办法</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><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">将read.md的内容重定向至write.md</span><br>cat > write.md < read.md<br><span class="hljs-meta prompt_"># </span><span class="language-bash"><span class="hljs-string">"<< EOF"</span>指对流进行重定向输入,直到遇到<span class="hljs-string">"EOF"</span>(可修改)作为末尾则结束</span><br>cat > input.md << EOF<br></code></pre></td></tr></table></figure><blockquote><p>摘自:<a href="https://stackoverflow.com/a/12168594">How does “<<” operator work in linux shell?</a></p><blockquote><p><code><<</code> 操作符主要有以下三个操作逻辑</p><ol><li>首先执行该操作符左侧的程序,在上面的例子中就是<code>cat</code></li><li>抓取用户包括换行等所有的输入内容,直到输入的内容为用户指定的EOF结尾内容(在上面的例子中则恰好也是EOF)则停止</li><li>将所有除了EOF的内容都作为(1)程序的标准输入执行</li></ol></blockquote></blockquote><p>实例:通过<code>bash</code>脚本创建一个<code>test.cpp</code>文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/bash</span><br><span class="hljs-built_in">cat</span> > test.cpp << <span class="hljs-string">EOF</span><br><span class="hljs-string">#include<iostream></span><br><span class="hljs-string">using namespace std;</span><br><span class="hljs-string"></span><br><span class="hljs-string">int main() {</span><br><span class="hljs-string"> cout << "This is stand output" << endl;</span><br><span class="hljs-string"> cerr << "This is error output" << endl;</span><br><span class="hljs-string"> return 0;</span><br><span class="hljs-string">}</span><br><span class="hljs-string">EOF</span><br></code></pre></td></tr></table></figure><h3 id="管道和重定向的使用区别"><a class="markdownIt-Anchor" href="#管道和重定向的使用区别"></a> 管道和重定向的使用区别</h3><blockquote><p>1、文件类型上<br />左边的命令应该有标准输出 | 右边的命令应该能接受标准输入;<br />左边的命令应该有标准输出 > 右边只能是文件;<br />左边的命令应该需要标准输入 < 右边只能是文件;</p><p>2、管道触发两个子进程执行 “|” 两边的程序,而重定向是在一个进程内执行。</p></blockquote><h4 id="结合管道的输入重定向"><a class="markdownIt-Anchor" href="#结合管道的输入重定向"></a> 结合管道的输入重定向</h4><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></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash">(sed -n <span class="hljs-string">'1,$p'</span> | grep -n <span class="hljs-string">'output'</span>) < test.cpp</span><br>5: cout << "This is stand output" << endl;<br>6: cerr << "This is error output" << endl;<br><span class="hljs-meta prompt_"># </span><span class="language-bash">等价于 sed -n <span class="hljs-string">'1,$p'</span> < test.cpp | grep -n <span class="hljs-string">'output'</span></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">对于管道运算符,如果希望将`test.cpp`传递给前面的第一个可执行文件`sed`,则需要使用单括号将整个管道传输看作一个单独的指令,否则`test.cpp`将传入`grep`内</span><br></code></pre></td></tr></table></figure><blockquote><p>由于标准输入和标准输出在管道运算符中的重定向是发生在"内容输出"之前的,因此可以通过重定向来修改管道中传输的数据</p><blockquote><p>Because pipeline assignment of standard input or standard output or both takes place before redirection, it can be modified by redirection.</p></blockquote></blockquote><p>举个例子来说,本来管道传输默认只传输标准输出的内容,并不会传输错误输出</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">[halc@Zephyrus ~]$ command1 2>&1 | command2<br></code></pre></td></tr></table></figure><p>通过上面的指令,首先<code>command1</code>的 <strong>错误输出</strong> 会在执行前被重定向至标准输入,然后<code>command1</code>执行,将 <strong>标准输出</strong> 和 <strong>错误输出</strong> 一并通过管道进行传输,作为<code>command2</code>的标准输入</p><h4 id="结合管道的输出重定向"><a class="markdownIt-Anchor" href="#结合管道的输出重定向"></a> 结合管道的输出重定向</h4><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><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">首先将test.sh的内容通过<span class="hljs-built_in">cat</span>打印到标准输出, 然后管道传输该输出给<span class="hljs-built_in">tee</span>,</span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">在<span class="hljs-built_in">tee</span>执行之前通过&>将<span class="hljs-built_in">tee</span>的标准输入和错误输出都重定向至/dev/null中,</span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">然后执行<span class="hljs-built_in">tee</span>将管道获取的内容写入text.txt,并且将相同的内容写入null设备当中</span><br>cat test.sh | tee text.txt &> /dev/null<br></code></pre></td></tr></table></figure><blockquote><p><code>></code> 输出重定向,往往在命令最右边(也可以用到命令中间),接收左边命令的输出结果,重定向到指定文件。</p></blockquote><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></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">如果管道符左侧的程序已经将标准重定向指向了其他文件,那么在bash中管道传输的数据将为空</span><br>[halc@Zephyrus ~]$ rg Word dict &>log | cat<br><span class="hljs-meta prompt_"># </span><span class="language-bash">此时无输出</span><br>[halc@Zephyrus ~]$ cat log<br>Word1<br>Word2<br>Word3<br><span class="hljs-meta prompt_"># </span><span class="language-bash">但是<span class="hljs-built_in">log</span>内应该是有内容匹配的</span><br></code></pre></td></tr></table></figure><h5 id="zsh下的重定向与管道传输"><a class="markdownIt-Anchor" href="#zsh下的重定向与管道传输"></a> zsh下的重定向与管道传输</h5><p>在自己实验的时候发现<code>zsh</code>下即使重定向了标准输出和错误输出依旧可以通过管道读取内容,这主要是zsh有一个可以将输出重定向给多个文件的特性,对于管道也会进行二次传递</p><blockquote><p>参考 <a href="https://stackoverflow.com/a/29783586">Redirection and pipe behavior in bash vs. zsh</a></p><blockquote><p>Read the MULTIOS documentation in the zshmisc man page. It’s a feature of zsh which causes it to redirect the output to multiple files at the same time, and it can also be a pipe.</p></blockquote></blockquote><p>具体举例</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">ls >a >b<br></code></pre></td></tr></table></figure><p>上面这个命令在<code>bash</code>当中只有文件<code>a</code>会有内容,而<code>b</code>中并没有获取到标准输出。但是在<code>zsh</code>下执行上面的命令,则<code>a</code>和<code>b</code>中都会拥有相同的输出内容。</p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>missing-semester</tag>
<tag>shell</tag>
</tags>
</entry>
<entry>
<title>快速部署rclone为services</title>
<link href="/p/bbde595d.html"/>
<url>/p/bbde595d.html</url>
<content type="html"><![CDATA[<h2 id="参考链接"><a class="markdownIt-Anchor" href="#参考链接"></a> 参考链接</h2><ul><li><a href="https://jtxiao.com/main/posts/rclone/">Rclone配置</a></li><li><a href="https://www.guyrutenberg.com/2021/06/25/autostart-rclone-mount-using-systemd/">Autostart rclone mount using systemd</a></li></ul><h2 id="安装rclone"><a class="markdownIt-Anchor" href="#安装rclone"></a> 安装Rclone</h2><p>在Linux上安装<code>rclone</code>可以直接使用默认发行版仓库的版本,也可以官方脚本安装</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">curl https://rclone.org/install.sh | sudo bash<br></code></pre></td></tr></table></figure><h2 id="配置rclone"><a class="markdownIt-Anchor" href="#配置rclone"></a> 配置Rclone</h2><p>安装完成了之后通过输入以下指令可以在交互式页面当中添加、修改或删除连接信息</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">rclone config<br></code></pre></td></tr></table></figure><h2 id="挂载和关闭挂载"><a class="markdownIt-Anchor" href="#挂载和关闭挂载"></a> 挂载和关闭挂载</h2><p>在配置完成之后,如果需要将<code>rclone</code>的内容挂载到本地,执行类似以下格式的指令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">rclone mount remote_name:path/to/directory path/to/mount_point<br></code></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></pre></td><td class="code"><pre><code class="hljs plain">--transfers:该参数控制最大同时传输任务数量,如果你cpu性能差,建议调低,但太低可能会影响多个文档同时传输的速度。<br>--buffer-size:该参数为读取每个文档时的内存缓冲区大小,控制rclone上传和挂载的时候的内存占用,调低点可以防止内存占用过高而崩溃,但太低可能会影响部分文档的传输速度。<br>--low-level-retries:该参数为传输文档没速度的时候重试次数,没速度的时候,单个会自动睡眠10ms起,然后再重试,不行,再睡眠更长一段时间,再重试,这样可以稍微加快文档上传进度。<br><br>下列参数主要是上传用的<br> --umask 0000 <br> --default-permissions <br> --allow-non-empty <br> --allow-other <br> --transfers 4 <br> --buffer-size 32M <br> --low-level-retries 200<br><br>如果你还涉及到读取使用,比如使用H5ai等在线播放,就还建议加3个参数,添加格式参考上面<br>--dir-cache-time 12h<br>--vfs-read-chunk-size 32M<br>--vfs-read-chunk-size-limit 1G<br>1. --vfs-cache-mode off<br> - 所有文件操作全部直接进行<br> - 失败无法自动重试<br>2. --vfs-cache-mode minimal<br> - 读写模式打开的文件将会首先缓冲到磁盘<br> - 其他模式打开文件直接进行操作<br> - 失败无法自动重试<br>3. --vfs-cache-mode writes<br> - 读写模式打开的文件将会首先缓冲到磁盘<br> - 只写模式打开的文件将会首先缓冲到磁盘<br> - 只读取模式打开的文件将会直接进行操作<br> - 支持自动重试<br>4. --vfs-cache-mode full<br> - 所有文件操作全部缓存<br> - 读取会下载整个文件<br></code></pre></td></tr></table></figure><p>在挂载的时候可以添加<code>--deamon</code>参数来让rclone后台临时挂载,如果要取消挂载则输入以下指令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">fusermount -u path/to/mount_point<br></code></pre></td></tr></table></figure><h2 id="通过systemd自启动挂载"><a class="markdownIt-Anchor" href="#通过systemd自启动挂载"></a> 通过systemd自启动挂载</h2><p>如果需要配置开机自启动挂载对应的Rclone服务,只需要创建以下文件(以onedrive为例,参数和名字可自定义):<code>~/.config/systemd/user/rclone-onedrive.service</code></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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><code class="hljs shell">[Unit]<br>Description=OneDrive (rclone)<br><span class="hljs-meta prompt_"># </span><span class="language-bash">挂载在当前用户的~目录的OneDrive文件夹内,需要提前创建好~/OneDrive</span><br>AssertPathIsDirectory=%h/OneDrive 、<br><span class="hljs-meta prompt_"># </span><span class="language-bash">Make sure we have network enabled</span><br>After=network.target<br><br>[Service]<br>Type=simple<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">启用vfs模式,将onedrive挂载给/home/xxx/OneDrive文件夹</span><br>ExecStart=/usr/bin/rclone mount onedrive:/ OneDrive --vfs-cache-mode full<br><span class="hljs-meta prompt_"># </span><span class="language-bash">取消挂载</span><br>ExecStop=/usr/bin/fusermount -zu %h/OneDrive<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_"># </span><span class="language-bash">Restart the service whenever rclone exists with non-zero <span class="hljs-built_in">exit</span> code</span><br>Restart=on-failure<br>RestartSec=15<br><br>[Install]<br><span class="hljs-meta prompt_"># </span><span class="language-bash">Autostart after reboot</span><br>WantedBy=default.target<br></code></pre></td></tr></table></figure><p>写入完成之后,通过执行以下两个指令在当前用户下生效该服务</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><code class="hljs shell">systemctl --user daemon-reload<br>systemctl --user enable --now rclone-onedrive<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>rclone</tag>
</tags>
</entry>
<entry>
<title>总结:2022年5月</title>
<link href="/p/795cfa1d.html"/>
<url>/p/795cfa1d.html</url>
<content type="html"><![CDATA[<p>这个月算是找回了感觉,还算充实。</p><h2 id="做了的事情"><a class="markdownIt-Anchor" href="#做了的事情"></a> 做了的事情</h2><h3 id="学习方面"><a class="markdownIt-Anchor" href="#学习方面"></a> 学习方面</h3><p>这个月应该算是最平常的一个月了,整个人谈不上有动力,也谈不上完全摆烂。月初刚开始的时候把OSTEP上操作系统虚拟化的部分基本上写完了,然后就开始关注开源之夏项目申请相关的内容。在之前某个学长的推荐下看过一篇<code>xmake-clangd</code>配置vscode写<code>cpp</code>环境的知乎的帖子,当时对于<code>cpp</code>编译和链接相关的知识还完全没有概念,在开源之夏的时候模模糊糊搜了下<code>xmake</code>发现居然有项目能申请,就怂恿和<code>gofaquan</code>一起报了相关的项目。在五月底到六月初花了大概一百多RMB学习了<code>cpp</code>常见的一些项目管理会遇到的问题,感觉还蛮值的,希望最后项目申请能成功中标,给暑假的自己找点事情做。</p><p>学校也快到期末了,这学期的大作业还算应付的过来,大部分大作业都能组队解决,不能组队的也不算麻烦,基本上<code>1-2</code>天的时间也都能搞定,希望下个月的期末考试能一切顺利,别挂科。</p><h3 id="生活方面"><a class="markdownIt-Anchor" href="#生活方面"></a> 生活方面</h3><p>这个月主要的学习部分应该也就是上半个月和六月份初了。五月中旬的时候学校终于有了解封相关的信息,于是开始到处想办法出去吃,报复性消费了好几顿过度饮食,体重直接涨了十多斤。现在期末考试临近,也没有什么减肥的想法,打算在考试前可能就通过不断的摄入碳水来缓解压力了,减肥啥的还是等考完期末考试再说了XD。</p><p>然后就是每次考试前都日常想要有的报复性消费。这学期把自己之前<code>Niz</code>那个108键的键盘换成了68配位的。目前过渡到日常使用已经没什么问题了,后面看看能不能开发点新功能,再提高提高效率(<s>其实就是差生文具多</s>)</p><h2 id="六月份要做的事情"><a class="markdownIt-Anchor" href="#六月份要做的事情"></a> 六月份要做的事情</h2><h3 id="学习计划"><a class="markdownIt-Anchor" href="#学习计划"></a> 学习计划</h3><ul><li>希望期末考试算顺利复习完,不挂科</li><li>能熟悉<code>xmake</code>要导入的包相关源码</li></ul><h3 id="生活计划"><a class="markdownIt-Anchor" href="#生活计划"></a> 生活计划</h3><p>在期末考试结束以前是不指望有什么生活质量了,只能寄托希望给暑假的自己了</p><ul><li>减肥!至少恢复160-以下</li><li>调整自己的作息,打破在家必摆烂的心态(感觉不太可能)</li></ul>]]></content>
<categories>
<category>个人总结</category>
</categories>
<tags>
<tag>总结</tag>
</tags>
</entry>
<entry>
<title>OSTEP:分页的计算</title>
<link href="/p/c1ec22c4.html"/>
<url>/p/c1ec22c4.html</url>
<content type="html"><![CDATA[<h2 id="第二十章分页较小的表"><a class="markdownIt-Anchor" href="#第二十章分页较小的表"></a> 第二十章:分页:较小的表</h2><ol><li><p>对于线性页表,只要知道第一个<code>Page</code>的地址,存于寄存器当中,就可以通过这个地址依次陆续推算下一个或后面任意一个有效的地址范围。对于多级页表,通过多次搜索,依旧可以在只有最初的页表的地址的情况下,通过多次的偏移查询来定位到最后需要的特定地址。</p></li><li><p>这里取例子说明算法,具体答案通过<code>-c</code>参数可直接输出</p><p>这里以<code>seed</code>为<code>0</code>的时候为例</p><p>首先,在<code>README.md</code>中可以得到以下信息</p><ul><li>Page Size: 32 bytes</li><li>Virtual Address Space:32 KB(1024个分页 2^15)<ul><li>虚拟地址需要 15 bits (<code>VPN</code> 占 10 bit,<code>offset</code> 占 5 bit)</li></ul></li><li>Physical Memory: 128个分页(2^12)<ul><li>物理地址需要 12 bits (<code>PFN</code> 占 7bit, <code>offset</code> 占 5 bit )</li></ul></li><li>Virtual Address Space 的前五位对应了<code>Page Directory</code>的索引</li></ul><p>通过<code>seed</code>生成<code>0</code>对应的地址数据,用于<code>PDE</code>查表,内容如下</p> <details> <summary>Page Content</summary> <figure class="highlight sh"><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></pre></td><td class="code"><pre><code class="hljs sh">page 0:1b1d05051d0b19001e00121c1909190c0f0b0a1218151700100a061c06050514<br>page 1:0000000000000000000000000000000000000000000000000000000000000000<br>page 2:121b0c06001e04130f0b10021e0f000c17091717071e001a0f0408120819060b<br>page 3:7f7f7f7fcd7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f887f7f7f7f7f7f7f7fb9<br>page 4:0b041004051c13071b131d0e1b150107080507071b0e1b0411001c000c181e00<br>page 5:17131d0a1202111906081507081d1e041b1101121301171902140e070e040a14<br>page 6:0000000000000000000000000000000000000000000000000000000000000000<br>page 7:0000000000000000000000000000000000000000000000000000000000000000<br>page 8:11101a120f10180a11151e151d0c12170a081e0a1e1a06191e08141702190915<br>page 9:0000000000000000000000000000000000000000000000000000000000000000<br>page 10:0000000000000000000000000000000000000000000000000000000000000000<br>page 11:0910141d04011a18170e150c050c18181d1b151016051c16120d13131b11060d<br>page 12:060b16191c05141d01141a0a07120d050e0c110f090b19071100160a0108071d<br>page 13:19100b0e000614140f1d0e091a08121519180b0101161d0a0d16140814090b10<br>page 14:1218140b000d1c0a07040f10020c141d0d0d0e060c140c12191e1b0b00120e07<br>page 15:0000000000000000000000000000000000000000000000000000000000000000<br>page 16:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fea7f7f7f<br>page 17:0000000000000000000000000000000000000000000000000000000000000000<br>page 18:7f7f7f7f7f7fab7f7f7f8e7f7f7fdd7f7f7f7f7f7f7f8b7f7f7f7f7f7f7f7f7f<br>page 19:00130001061402011e0d1b060d0b050a1e170b0c081016150e011c0c0c00041a<br>page 20:1a190402020c1d110807030419041a190411001a11170f151c111b0a03000719<br>page 21:0b081b0e1c151e121e050d111e111a130f0c0b09061d101a1b1d070a13090417<br>page 22:1212150f081b0a0e130f1d1d1c1c120f150608010500140418151e0c1c0e0a03<br>page 23:1d0f030b0c0f1e1e1113140f0f091502091b071d1e110102060a03180b07010b<br>page 24:0000000000000000000000000000000000000000000000000000000000000000<br>page 25:03031c031b0e0e0a0c0b110a1907070e1c0016000c170d0d070e070814121c1e<br>page 26:090e1d18081115180d0c170d070e1d040e130e06001513000917131004150e15<br>page 27:0000000000000000000000000000000000000000000000000000000000000000<br>page 28:0f1d0f0a0211070b0b17071d170e1b0b0b04180c0f0e140b1c0d0b0c171e1a0e<br>page 29:17081e031b010710120c030708171c120118090a10071c050c08101113100c13<br>page 30:7f7f7f7f7f847f7f7f7f977fbd7f7ff47f7f7f7f7f7f7f7f7f7f7f7f7f7f9c7f<br>page 31:7f7f7f7f7f7fd07f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 32:0000000000000000000000000000000000000000000000000000000000000000<br>page 33:7f7f7f7f7f7f7f7fb57f9d7f7f7f7f7f7f7f7f7f7f7f7f7f7f7ff6b17f7f7f7f<br>page 34:0413050d0c02161518101105060710190b1b16160a031d1a0c1a1b0a0f0a151c<br>page 35:0000000000000000000000000000000000000000000000000000000000000000<br>page 36:1d1313160c0c1400050a07130b1b110c0c150c14010d0804100f11171b0f090e<br>page 37:1e0f0a0d0c100c021e1e05070d15001913081a1409101e01151a150412180c12<br>page 38:0000000000000000000000000000000000000000000000000000000000000000<br>page 39:1b111e171108150e160c0f001601151218081506100a1e1e06110a1e1c121615<br>page 40:0d030b1007190b0709191c1d0017100307080c0e1d01151a0b07060904110700<br>page 41:7f7f7f7f7f7f7f7fe57f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f8d7f7f7f7f7f<br>page 42:03041501111c1015001312110c0b1e01001d050306181d000d030806140a050f<br>page 43:190802041311011e0e0916000d141d171b030d00080b0a0b180519100a11050f<br>page 44:7f7f7f7f7f7fcc7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fa27f7f7f7f7f7f<br>page 45:7fb27fef7f7f7f7fa4f57f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 46:0000000000000000000000000000000000000000000000000000000000000000<br>page 47:070a0f1002090b0c0e0d020613190f0402040b111410110a14160c19171c0e0a<br>page 48:0000000000000000000000000000000000000000000000000000000000000000<br>page 49:1e0a0f0702030d13101003010b1d05080e1c1d00140714171b151a1804011610<br>page 50:161b040706011a0f020d0d181704130f0004140b1d0f15040e1619060c0e0d0e<br>page 51:14000f1a070a1a0511071d180d02090f1c0311151019101d12120d120b110905<br>page 52:0000000000000000000000000000000000000000000000000000000000000000<br>page 53:0f0c18090e121c0f081713071c1e191b09161b150e030d121c1d0e1a08181100<br>page 54:1901050f031b1c090d11081006090d121008070318031607081614160f1a0314<br>page 55:0000000000000000000000000000000000000000000000000000000000000000<br>page 56:0000000000000000000000000000000000000000000000000000000000000000<br>page 57:1c1d1602020b000a001e19021b0606141d03000b00121a05030a1d041d0b0e09<br>page 58:0000000000000000000000000000000000000000000000000000000000000000<br>page 59:0000000000000000000000000000000000000000000000000000000000000000<br>page 60:0000000000000000000000000000000000000000000000000000000000000000<br>page 61:010510020c0a0c031c0e1a1e0a0e150d09161b1c130b1e1302021701000c100d<br>page 62:7f7f7fa87f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 63:0612060a1d1b19010407181a12161902021a010601001a0a0404141e0f1b0f11<br>page 64:18121708080d1e161d10111e0518181a1704141c110b1d110c13180700101d15<br>page 65:7f7f7f7f7f7f7f7f7f7f997f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 66:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fd77f7f<br>page 67:0000000000000000000000000000000000000000000000000000000000000000<br>page 68:121216020f060c0f0a0c16011d120511020f150d09141c1b0b1a03011e171311<br>page 69:190a19020d0a0d190f1e1a03090016001b050c01090c0117160b1902010b1b17<br>page 70:0000000000000000000000000000000000000000000000000000000000000000<br>page 71:7f7f7f7f7f7f7f7f7f7f7f857f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 72:180c0018050c0b030a051314000e111b0f02011a181a081402190a1d0e011c13<br>page 73:0000000000000000000000000000000000000000000000000000000000000000<br>page 74:0d0b1e08180d0b011a151b0d14030c06011d0604060b10041e1e040c151b0f1c<br>page 75:1a1c011b00141c0f0c0a1c1c13160a041e14081e120a1b021804030816120d04<br>page 76:0c11150c1b1d1e01191b041d03061d191108070c0013011702000817190f1d03<br>page 77:1c061606001b1a0205071c0b190d0b171308121519141312021d16081513140b<br>page 78:0e02171b1c1a1b1c100c1508191a1b121d110d141e1c1802120f131a07160306<br>page 79:1e1b1516071708030e0a050d1b0d0d1510041c0d180c190c06061d12010c0702<br>page 80:1b081d1c020d170d0f19151d051c1c131d071b171202000007170b18130c1b01<br>page 81:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fe27f7f7f7f7f7f7f7f7f7f7f7f7ffa<br>page 82:0000000000000000000000000000000000000000000000000000000000000000<br>page 83:0000000000000000000000000000000000000000000000000000000000000000<br>page 84:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f947f7f7f7f7fce<br>page 85:7f7f7f7f7f7f7f7f9a7fbf7f7f7f7f7f7f7f7f7faf7f7f7f7f7f7f7f7f7f7f7f<br>page 86:7f7f7f7f7f7f7fc57f7f7f7f7f7f7f7f7f7f7f7fca7f7fee7f7f7f7f7f7f7f7f<br>page 87:1805180d170e1802011c0f1b1d14110602191b18150d09030d111c1d0c031716<br>page 88:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fc47f7f7f7f7f7f7f7f7f7f7f7f<br>page 89:0000000000000000000000000000000000000000000000000000000000000000<br>page 90:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fc07f7f7f7f7f7f7f7fde7f7f7f7f7f7f<br>page 91:7f7f7f7f7f7f7f7f7f7f7f7fa57f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 92:0000000000000000000000000000000000000000000000000000000000000000<br>page 93:0a1a1907001905181505021c12130e0412071816001c01020904070b160c080f<br>page 94:1406190710140713080519110a1200040c1e0f021718181115061619170a1213<br>page 95:0a1d0f1d1e1915040012151d10151406131e0315130b18001b190e030e12070f<br>page 96:7f7f7f7f7f7f7f7f7f7f7f7f7f7fb67f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 97:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fc87f7f7f7f7fe77f7f7f7f7f7f7f7f7f<br>page 98:15191803171a170e1503170818130f100201001804030b1e1b0919020c111e01<br>page 99:090b1304150b1204140a0e0c0e1509140109170113000e1b0010021a15171400<br>page 100:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fa77f7f7f7f7f7f7f7f7f7fe37f7f<br>page 101:0e0a00010b061005061416091a070a16011c020e1601191e0e030203170c1c0d<br>page 102:1d031b0116000d1a0c1c1612050a0c121e080f1c0a13171317061d0512091309<br>page 103:1e171c061012190e180c121a181400050f07021a1d090c19011303081901010c<br>page 104:7f7f7f7f7f7f7f7f7f7f7f7f80aa7f7f7f7f7f7f7f7f7f7f7f7f7f7ff07f7f7f<br>page 105:b37f7f7f7f7f7f7f7f7f7f7f7f937f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 106:160a000e1001110a00050310011c1a1d091c1e170814120c090103040e131701<br>page 107:7f7f7f7f7f7f7f7f7f7f7f7f7f7ff17f7f7f7f7f7f7f7f7ff37f7f7f7f7f7f7f<br>page 108:83fee0da7fd47febbe9ed5ade4ac90d692d8c1f89fe1ede9a1e8c7c2a9d1dbff<br>page 109:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f827f7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 110:1614041e0c120b010e0401131303110a0b180f1b120e130a03151318031c181c<br>page 111:08000115111d1d1c01171514161b130b10061200040a18160a1301051e080c11<br>page 112:19051e1302161e0c150906160019100303141b081e031a0c02080e181a041014<br>page 113:1d07111b1205071e091a181716181a01050f06100f03020019021d1e170d080c<br>page 114:0000000000000000000000000000000000000000000000000000000000000000<br>page 115:110601040d1406151a170d141e1b0a1505110b0d0d141a0e0417171d0c0e101b<br>page 116:0a130b11150f14171a05060f0f19101b180f190e0a0d0e1401161e0e02060307<br>page 117:1b0a170019111d0b130a18121e000401031c1d0e1d19181705110d1d05051404<br>page 118:1119021a1c05191a1b101206150c00040c1b111c1c02120a0f0e0e03190f130e<br>page 119:0000000000000000000000000000000000000000000000000000000000000000<br>page 120:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fcb7f7f7f7f7f7f7f7f7f7f7f7f7f<br>page 121:0000000000000000000000000000000000000000000000000000000000000000<br>page 122:051e0312041b1d18090717090d01040002020d1116040d13020d0b1d010c0c16<br>page 123:0000000000000000000000000000000000000000000000000000000000000000<br>page 124:0000000000000000000000000000000000000000000000000000000000000000<br>page 125:0000000000000000000000000000000000000000000000000000000000000000<br>page 126:7f7f7f7f7f7f7f7f8ce6cf7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f967f7f7f7f7f<br>page 127:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fdf7f7f7f7f7f7f7f7f7f7f7f7f957f7f<br></code></pre></td></tr></table></figure> </details><p>并且根据模拟生成的<code>PDBR: 108</code>可以知道<code>Page 108</code>对应的内容即是第一级<code>PDE</code>的映射</p><p>接下来就可以进行计算了,这里取一个<code>vaild</code>和<code>invaild</code>的答案分析计算过程</p> <figure class="highlight sh"><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><code class="hljs sh">Virtual Address 611c:<br> --> pde index:0x18 [decimal 24] pde contents:0xa1 (valid 1, pfn 0x21 [decimal 33])<br> --> pte index:0x8 [decimal 8] pte contents:0xb5 (valid 1, pfn 0x35 [decimal 53])<br> --> Translates to Physical Address 0x6bc --> Value: 08<br>Virtual Address 3da8:<br> --> pde index:0xf [decimal 15] pde contents:0xd6 (valid 1, pfn 0x56 [decimal 86])<br> --> pte index:0xd [decimal 13] pte contents:0x7f (valid 0, pfn 0x7f [decimal 127])<br> --> Fault (page table entry not valid)<br></code></pre></td></tr></table></figure><p>Virtual Address <code>611c</code></p><ol><li><p>将611c按二进制转换为<code>Virtual Address Space</code>对应的 15 bits 为 <code>11000 01000 11100</code>。对<code>PDE</code>分割前五位有<code>11000(decimal 24)</code></p></li><li><p>这个时候对最顶层的<code>PDTR</code>对应的<code>Page 108</code>进行查表操作</p></li></ol><p>首先可以通过<code>Page Content</code>得到<code>Page 108</code>内容如下</p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sh">Page 108: 83 fe e0 da 7f d4 7f eb be 9e d5 ad e4 ac 90 d6 92 d8 c1 f8 9f e1 ed e9 a1 e8 c7 c2 a9 d1 db ff<br>Index: 0 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<br></code></pre></td></tr></table></figure><p>通过查表找到对应<code>Index</code>的内容可以知道<code>24(11000)</code>对应位置的内容是<code>a1</code>,转换为二进制为<code>1010 0001</code>,读取最高位为<code>1</code>可以知道是<code>vaild</code>。再通过后面的<code>0010 0001</code>转换为十进制为<code>33</code>,因此再继续查询<code>Page 33</code>的内容</p> <figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sh">Page 33:7f 7f 7f 7f 7f 7f 7f 7f b5 7f 9d 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f f6 b1 7f 7f 7f 7f<br>Index: 0 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<br></code></pre></td></tr></table></figure><p><code>611c</code>的[1-5]位为<code>PDE</code>,这时我们则需要通过[6-10]位来通过<code>PTE</code>寻找<code>VPN</code>对应的<code>PFN</code>地址。由<code>611c</code>为<code>11000 01000 11100</code>可以知道这次为<code>01000</code>即<code>(decmial 8)</code>,对照上面<code>page 33</code>可以发现第八个对应的内容为<code>53</code>,因此可以知道最终<code>VPN</code>对应上了<code>PFN</code>的<code>Page 53</code>。最后再通过<code>offset</code>的<code>11100</code>,配合<code>Page 53</code>对应的二进制<code>PFN</code>地址<code>11 0101</code>结合<code>offset</code>的<code>11100</code>有<code>110101 11100</code>,也就是最后答案的<code>0x6bc</code>,其中通过<code>offset</code>在<code>Page 53</code>中查表可以找到对应的 <code>Value</code> 为 <code>08</code></p><p>Virtual Address <code>3da8</code></p><ol><li><p>和<code>611c</code>同理,将<code>3da8</code>转为二进制<code>01111 01101 01000</code>,得到<code>pde index</code>为<code>01111(15)</code>,对应的<code>Value</code>为<code>0xd6(1101 0110)</code>,通过<code>1101 0110</code>最高位可以知道目标分页为<code>vaild</code>,去掉<code>vaild bit</code>可以得到索引的<code>pte</code>为<code>0x56(0101 0110 | 86)</code></p></li><li><p>检索<code>Page 86</code>,由<code>PTE index</code>为<code>01101</code>可以找到对应的<code>Value</code>为<code>0x7f(0111 1111)</code>由于最高位为<code>0</code>,因此是<code>invaild</code>,也就是说<code>pte</code>无效,无法访问</p></li></ol><p>其他的情况也是依次类推,即可算出结果是否有效</p></li><li><p>根据个人理解,具体缓存<code>hit</code>或<code>miss</code>的概率主要由对应内存上的数据决定。对于访问次数较多的内容<code>hit</code>缓存加快速度的概率自然更高;而对于不重复的多次查询(比如上面的练习),自然<code>miss</code>的概率会更高。因此对于进程来说,如果能将数据尽量集中在某一块分页上则能有效提高内存访问和处理的速度。</p></li></ol>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>OSTEP:TLB缓存命中和非命中的开销差距</title>
<link href="/p/20416971.html"/>
<url>/p/20416971.html</url>
<content type="html"><![CDATA[<h2 id="第十九章实际操作系统的tlb表项"><a class="markdownIt-Anchor" href="#第十九章实际操作系统的tlb表项"></a> 第十九章:实际操作系统的TLB表项</h2><p>本章为测量实验,主要要求为写一份<code>tlb.c</code>来测试在<code>TLB miss</code>和<code>TLB hit</code>的情况下性能开销的变化,以感受<code>TLB</code>的重要性</p><p>对于题中问题的回答</p><ol><li><p>由于<code>gettimeofday()</code>的函数只能精确到微秒,不足以测试较为精确的时间,因此使用<code>CLOCK_PROCESS_CPUTIME_ID</code>和<code>clock_gettime();</code>搭配即可获得纳秒级的时间测量,具体代码实现如下</p></li><li><p>具体代码实现如下</p><details><summary>tlb.c</summary><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> _GNU_SOURCE</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/time.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><pthread.h></span></span><br><br><span class="hljs-comment">// 将进程锁定在某个固定CPU上</span><br><span class="hljs-type">void</span> <span class="hljs-title function_">lockCpu</span><span class="hljs-params">(<span class="hljs-type">int</span> cpuId)</span><br>{<br> <span class="hljs-type">cpu_set_t</span> mask;<br> CPU_ZERO(&mask);<br> CPU_SET(cpuId, &mask);<br> <span class="hljs-keyword">if</span> (sched_setaffinity(<span class="hljs-number">0</span>, <span class="hljs-keyword">sizeof</span>(mask), &mask) < <span class="hljs-number">0</span>)<br> {<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"set thread affinity failed\n"</span>);<br> }<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> *argv[])</span><br>{<br> <span class="hljs-keyword">if</span> (argc != <span class="hljs-number">3</span>)<br> {<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Usage: ./tlb pages trials"</span>);<br> <span class="hljs-built_in">exit</span>(EXIT_FAILURE);<br> }<br><br> <span class="hljs-comment">// 将进程锁定在CPU0上</span><br> lockCpu(<span class="hljs-number">0</span>);<br><br> <span class="hljs-comment">// 申请页的数量</span><br> <span class="hljs-type">int</span> page_numebr = atoi(argv[<span class="hljs-number">1</span>]);<br> <span class="hljs-type">int</span> trials = atoi(argv[<span class="hljs-number">2</span>]);<br><br> <span class="hljs-keyword">if</span> (page_numebr <= <span class="hljs-number">0</span>)<br> {<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"Invaild Input"</span>);<br> <span class="hljs-built_in">exit</span>(EXIT_FAILURE);<br> }<br><br> <span class="hljs-type">int</span> jump = sysconf(_SC_PAGE_SIZE) / <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>);<br><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">timespec</span> <span class="hljs-title">start</span>, <span class="hljs-title">end</span>;</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">timespec</span> <span class="hljs-title">start_hit</span>, <span class="hljs-title">end_hit</span>;</span><br><br> <span class="hljs-type">int</span> sum_miss = <span class="hljs-number">0</span>;<br> <span class="hljs-type">int</span> sum_hit = <span class="hljs-number">0</span>;<br><br> <span class="hljs-type">int</span> cnt = <span class="hljs-number">0</span>;<br><br> <span class="hljs-keyword">while</span> (trials--)<br> {<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> step = <span class="hljs-number">0</span>; step < page_numebr * jump; step += jump)<br> {<br> cnt++;<br> <span class="hljs-type">int</span> *<span class="hljs-built_in">array</span> = <span class="hljs-built_in">calloc</span>(page_numebr, getpagesize());<br><br> <span class="hljs-comment">// 计算TLB miss的时间</span><br> clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start);<br> <span class="hljs-built_in">array</span>[step] += <span class="hljs-number">0</span>;<br> clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);<br> sum_miss += end.tv_nsec - start.tv_nsec;<br><br> <span class="hljs-comment">// 计算TLB hit的时间</span><br> clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_hit);<br> <span class="hljs-built_in">array</span>[step + <span class="hljs-number">1</span>] += <span class="hljs-number">0</span>;<br> clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_hit);<br> sum_hit += end_hit.tv_nsec - start_hit.tv_nsec;<br> <span class="hljs-built_in">free</span>(<span class="hljs-built_in">array</span>);<br> }<br> }<br> <span class="hljs-type">int</span> miss_average = sum_miss / cnt;<br> <span class="hljs-type">int</span> hit_average = sum_hit / cnt;<br><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Time per access(TLS miss): %d\n"</span>, miss_average);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Time per access(TLS hit): %d\n"</span>, hit_average);<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure></details><p>该程序主要思路为</p><ol><li>统计访问内存需要的总时间</li><li>首先统计<code>TLB miss</code>的情况,在<code>miss</code>之后<code>TLB</code>被激活</li><li>统计对应分页内存的后一位,此时<code>TLB hit</code>,能够成功快速定位</li><li>将总时间除以操作的总次数,得到最后的平均每次时间(单位为<code>ns</code>)</li></ol><p>大致结果如下</p><figure class="highlight sh"><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><code class="hljs sh">> ./tlb 1000 10<br>Time per access(TLS miss): 2105<br>Time per access(TLS hit): 223<br></code></pre></td></tr></table></figure><p>可以明显发现未命中的时候访问时间要远高于<code>TLB hit</code>时的时间</p></li><li><p>通过<code>Python</code>对<code>tlb</code>进行调用,大致结果为<code>4.</code>的效果</p></li><li><p>统计结果如下</p><p><img src="https://lsky.halc.top/ZAwW4O.png" alt="TLB统计" /></p><p>其中蓝色对应<code>TLB miss</code>的时间,橙色对应了<code>TLB hit</code>的开销时间</p><p>Python画图代码如下</p><details><summary>Matplotlib</summary><figure class="highlight python"><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">import</span> sys<br><span class="hljs-keyword">import</span> re<br><span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt<br><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">execRelocation</span>(<span class="hljs-params">page_number, trival</span>):<br> r = os.popen(<br> <span class="hljs-string">'./tlb %d %d'</span> % (page_number, trival))<br> text = r.read()<br> pattern = <span class="hljs-string">r"(\d+)"</span><br> tlb = re.findall(pattern, text)<br> r.close()<br> <span class="hljs-keyword">return</span> tlb<br><br><br>page_number = sys.argv[<span class="hljs-number">1</span>]<br>trival = sys.argv[<span class="hljs-number">2</span>]<br> <br>hit_time_access = []<br>miss_time_access = []<br>vpn_n = []<br> <br><span class="hljs-keyword">for</span> vpn <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, <span class="hljs-built_in">int</span>(page_number), <span class="hljs-number">128</span>):<br> <span class="hljs-built_in">print</span>(<span class="hljs-built_in">str</span>(vpn) + <span class="hljs-string">"/"</span> + <span class="hljs-built_in">str</span>(page_number))<br> tlb = execRelocation(vpn, <span class="hljs-built_in">int</span>(trival))<br> hit_time_access.append(<span class="hljs-built_in">int</span>(tlb[<span class="hljs-number">0</span>]))<br> miss_time_access.append(<span class="hljs-built_in">int</span>(tlb[<span class="hljs-number">1</span>]))<br> vpn_n.append(vpn)<br> <br>plt.xlabel(<span class="hljs-string">"Virtual Page Number"</span>)<br>plt.ylabel(<span class="hljs-string">"Time Per Access"</span>)<br> <br>plt.scatter(vpn_n, hit_time_access, label=<span class="hljs-string">"Hit"</span>)<br>plt.scatter(vpn_n, miss_time_access, label=<span class="hljs-string">"Miss"</span>)<br> <br>plt.savefig(<span class="hljs-string">"./paging.png"</span>)<br></code></pre></td></tr></table></figure></details></li><li><p>添加<code>-O0</code>的参数可以防止<code>gcc</code>在编译的时候不进行优化</p><p>示例如下</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sh">gcc -O0 tlb.c -o tlb<br></code></pre></td></tr></table></figure></li><li><p>在<a href="./2022-03-25-ostep_hw_3.md">上下文切换</a>时,解决方案为使用<code>Docker</code>创建一个单核的虚拟机来进行实验操作,这次实验中,使用<code>sched_setaffinity</code>函数来设置进程对CPU亲和力,以让程序在某一单一CPU上运行。</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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">define</span> _GNU_SOURCE</span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/time.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><pthread.h></span></span><br><br><span class="hljs-comment">// 将进程锁定在某个固定CPU上</span><br><span class="hljs-type">void</span> <span class="hljs-title function_">lockCpu</span><span class="hljs-params">(<span class="hljs-type">int</span> cpuId)</span><br>{<br> <span class="hljs-type">cpu_set_t</span> mask;<br> CPU_ZERO(&mask);<br> CPU_SET(cpuId, &mask);<br> <span class="hljs-keyword">if</span> (sched_setaffinity(<span class="hljs-number">0</span>, <span class="hljs-keyword">sizeof</span>(mask), &mask) < <span class="hljs-number">0</span>)<br> {<br> <span class="hljs-built_in">fprintf</span>(<span class="hljs-built_in">stderr</span>, <span class="hljs-string">"set thread affinity failed\n"</span>);<br> }<br>}<br></code></pre></td></tr></table></figure></li><li><p>通过使用<code>calloc()</code>函数,可以在对堆内的变量进行分配内存的同时进行初始化操作,并且在每一次循环进行之前都销毁数组重新创建,可以减少对实验测试的影响</p></li></ol>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>OSTEP:分页的原理</title>
<link href="/p/44838b9c.html"/>
<url>/p/44838b9c.html</url>
<content type="html"><![CDATA[<h2 id="第十八章分页介绍"><a class="markdownIt-Anchor" href="#第十八章分页介绍"></a> 第十八章:分页介绍</h2><ol><li><p>在虚拟地址中,<code>vpn</code>=<code>address space size</code>/<code>page size</code>. 所以在分页大小不变的情况下增加址空间大小会增加分页数量,在地址空间大小不变的情况下增加分页大小会减少分页数量。</p><p>如果使用了很大的分页,当程序只需要很小一部分内存的时候依旧会申请过大的内存,造成不必要的内存浪费</p></li><li><p>每次当某个分页被地址空间使用后,<code>PTE</code>中对应的<code>Vaild Bit</code>就会置为<code>1</code>。当提高<code>used paged</code>数量后,操作系统总是能找到<code>vpn</code>对应的<code>pfn</code></p></li><li><p>在这三种分配来说,前两种分页的大小相对于地址空间本身来说太大了,而在第三个例子当中,相对于<code>256m</code>,修改分页大小<code>1m</code>为更小的值将更加有助于提高空间的利用效率。</p></li><li><p>首先,地址空间和物理空间的大小都要是分页大小的倍数,其次,物理空间必须要比地址空间更大,否则会无法访问对应的地址空间。</p></li></ol>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>OSTEP:内存碎片的管理</title>
<link href="/p/3d85131.html"/>
<url>/p/3d85131.html</url>
<content type="html"><![CDATA[<h2 id="第十七章空闲空间管理"><a class="markdownIt-Anchor" href="#第十七章空闲空间管理"></a> 第十七章:空闲空间管理</h2><ol><li><p>执行题目给出的参数可以得到以下内容</p> <details> <summary> answer </summary> <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></pre></td><td class="code"><pre><code class="hljs log">seed 0<br>size 100<br>baseAddr 1000<br>headerSize 0<br>alignment -1<br>policy BEST<br>listOrder ADDRSORT<br>coalesce False<br>numOps 10<br>range 10<br>percentAlloc 50<br>allocList<br>compute True<br><br>ptr[0] = Alloc(3) returned 1000 (searched 1 elements)<br>Free List [ Size 1 ]: [ addr:1003 sz:97 ]<br><br>Free(ptr[0])<br>returned 0<br>Free List [ Size 2 ]: [ addr:1000 sz:3 ][ addr:1003 sz:97 ]<br><br>ptr[1] = Alloc(5) returned 1003 (searched 2 elements)<br>Free List [ Size 2 ]: [ addr:1000 sz:3 ][ addr:1008 sz:92 ]<br><br>Free(ptr[1])<br>returned 0<br>Free List [ Size 3 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:92 ]<br><br>ptr[2] = Alloc(8) returned 1008 (searched 3 elements)<br>Free List [ Size 3 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1016 sz:84 ]<br><br>Free(ptr[2])<br>returned 0<br>Free List [ Size 4 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1016 sz:84 ]<br><br>ptr[3] = Alloc(8) returned 1008 (searched 4 elements)<br>Free List [ Size 3 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1016 sz:84 ]<br><br>Free(ptr[3])<br>returned 0<br>Free List [ Size 4 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1016 sz:84 ]<br><br>ptr[4] = Alloc(2) returned 1000 (searched 4 elements)<br>Free List [ Size 4 ]: [ addr:1002 sz:1 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1016 sz:84 ]<br><br>ptr[5] = Alloc(7) returned 1008 (searched 4 elements)<br>Free List [ Size 4 ]: [ addr:1002 sz:1 ][ addr:1003 sz:5 ][ addr:1015 sz:1 ][ addr:1016 sz:84 ]<br></code></pre></td></tr></table></figure> </details><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></pre></td><td class="code"><pre><code class="hljs log">ptr[0] = Alloc(3) returned 1000 (searched 1 elements)<br>Free List [ Size 1 ]: [ addr:1003 sz:97 ]<br><br>Free(ptr[0])<br>returned 0<br>Free List [ Size 2 ]: [ addr:1000 sz:3 ][ addr:1003 sz:97 ]<br><br>ptr[1] = Alloc(5) returned 1003 (searched 2 elements)<br>Free List [ Size 2 ]: [ addr:1000 sz:3 ][ addr:1008 sz:92 ]<br><br>Free(ptr[1])<br>returned 0<br>Free List [ Size 3 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:92 ]<br></code></pre></td></tr></table></figure><p>首先,根据开头参数可以知道目前分配方式不考虑<code>Header</code>头指针,并且不考虑内存合并</p><p><code>ptr[0] = Alloc(3)</code>会从<code>1000</code>开始分配大小为<code>3</code>的内存(此时空间足够,分配成功),并返回<code>1000</code>的地址作为开头。此时空闲空间<code>Free List</code>只有一块,从<code>1003</code>开始,总大小为<code>97</code></p><p>之后的<code>Free(ptr[0])</code>则将刚刚分配的内存释放,释放后由于并未合并,因此出现了<code>[3]->[97]</code>的空闲空间表,之后再进行<code>ptr[1] = Alloc(5)</code>。由于第一个空闲空间为<code>[3]</code>不足以容纳<code>[5]</code>的分配,因此第二个空间是从<code>1003</code>作为内存开始的地址,并使用了<code>5</code>的内存空间</p><p>然后再进行<code>Free(ptr[1])</code>的操作释放内存并且不合并,则有了<code>[3]->[5]->[92]</code>的空闲可用空间。后面的运算以此类推</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><code class="hljs log">ptr[4] = Alloc(2) returned 1000 (searched 4 elements)<br>Free List [ Size 4 ]: [ addr:1002 sz:1 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1016 sz:84 ]<br></code></pre></td></tr></table></figure><p>则占用部分内存后,新的剩余内存将被作为新的内存段保留使用,并且已分配内存也会产生新的内存碎片</p></li><li><p>使用最差匹配有如下输出</p> <details> <summary> answer </summary> <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></pre></td><td class="code"><pre><code class="hljs log">seed 0<br>size 100<br>baseAddr 1000<br>headerSize 0<br>alignment -1<br>policy WORST<br>listOrder ADDRSORT<br>coalesce False<br>numOps 10<br>range 10<br>percentAlloc 50<br>allocList<br>compute True<br><br>ptr[0] = Alloc(3) returned 1000 (searched 1 elements)<br>Free List [ Size 1 ]: [ addr:1003 sz:97 ]<br><br>Free(ptr[0])<br>returned 0<br>Free List [ Size 2 ]: [ addr:1000 sz:3 ][ addr:1003 sz:97 ]<br><br>ptr[1] = Alloc(5) returned 1003 (searched 2 elements)<br>Free List [ Size 2 ]: [ addr:1000 sz:3 ][ addr:1008 sz:92 ]<br><br>Free(ptr[1])<br>returned 0<br>Free List [ Size 3 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:92 ]<br><br>ptr[2] = Alloc(8) returned 1008 (searched 3 elements)<br>Free List [ Size 3 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1016 sz:84 ]<br><br>Free(ptr[2])<br>returned 0<br>Free List [ Size 4 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1016 sz:84 ]<br><br>ptr[3] = Alloc(8) returned 1016 (searched 4 elements)<br>Free List [ Size 4 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1024 sz:76 ]<br><br>Free(ptr[3])<br>returned 0<br>Free List [ Size 5 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1016 sz:8 ][ addr:1024 sz:76 ]<br><br>ptr[4] = Alloc(2) returned 1024 (searched 5 elements)<br>Free List [ Size 5 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1016 sz:8 ][ addr:1026 sz:74 ]<br><br>ptr[5] = Alloc(7) returned 1026 (searched 5 elements)<br>Free List [ Size 5 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1016 sz:8 ][ addr:1033 sz:67 ]<br></code></pre></td></tr></table></figure> </details><p>比对可以发现,在最差匹配的情况下,第二次重复分配<code>8</code>大小的空间时不会重复利用本身空闲的长度为<code>8</code>的空间,而是依旧从后面的最大空闲空间中分配<code>8</code>大小的空间出来,此时地址从<code>1024</code>开始分配,并且<code>1008</code>开头的地址不会被复用</p> <figure class="highlight diff"><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><code class="hljs diff">Free(ptr[2])<br>returned 0<br>Free List [ Size 4 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1016 sz:84 ]<br><br><span class="hljs-comment">--- BEST</span><br><span class="hljs-deletion">- ptr[3] = Alloc(8) returned 1008 (searched 4 elements)</span><br><span class="hljs-deletion">- Free List [ Size 3 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1016 sz:84 ]</span><br><br><span class="hljs-comment">+++ WORSE</span><br><span class="hljs-addition">+ ptr[3] = Alloc(8) returned 1016 (searched 4 elements)</span><br><span class="hljs-addition">+ Free List [ Size 4 ]: [ addr:1000 sz:3 ][ addr:1003 sz:5 ][ addr:1008 sz:8 ][ addr:1024 sz:76 ]</span><br></code></pre></td></tr></table></figure></li><li><p>在使用<code>FIRST</code>进行匹配的情况下,空间分配的情况与<code>BEST</code>无异,在本例题示范当中,由于<code>FIRST</code>是找到可以用的内容后直接进行匹配,而示例当中恰好所有可用空间最小的情况匹配了<code>FIRST</code>的情况,因此只有搜索的次数变少,效率变快,其他差异不大。</p> <figure class="highlight diff"><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><code class="hljs diff"><span class="hljs-addition">+ policy FIRST</span><br><span class="hljs-comment">---</span><br><span class="hljs-deletion">- policy BEST</span><br><br><span class="hljs-addition">+ ptr[3] = Alloc(8) returned 1008 (searched 3 elements)</span><br><span class="hljs-comment">---</span><br><span class="hljs-deletion">- ptr[3] = Alloc(8) returned 1008 (searched 4 elements)</span><br><br><span class="hljs-addition">+ ptr[4] = Alloc(2) returned 1000 (searched 1 elements)</span><br><span class="hljs-comment">---</span><br><span class="hljs-deletion">- ptr[4] = Alloc(2) returned 1000 (searched 4 elements)</span><br><br><span class="hljs-addition">+ ptr[5] = Alloc(7) returned 1008 (searched 3 elements)</span><br><span class="hljs-comment">---</span><br><span class="hljs-deletion">- ptr[5] = Alloc(7) returned 1008 (searched 4 elements)</span><br></code></pre></td></tr></table></figure></li><li><p>三种不同的参数分别对应了以下三种不同的内存分配方式</p><ul><li><code>ADDSORT</code>: 空闲的地址在搜索的时候是按地址的顺序进行排序搜索</li><li><code>SIZESORT-</code>: 空闲的地址在搜索的时候按地址块的大小,先搜索大的,后搜索小的</li><li><code>SIZESORT+</code>: 空闲的地址在搜索的时候按地址快的大小,先搜索小的,后搜索大的</li></ul><p>由于<code>BEST</code>和<code>WORSE</code>分配的方法都需要把所有内存段全部搜索后再分配,因此在根据这两种不同的调度之后产生的内存碎片的数量是相同的。在三种不同的调度中采取<code>FIRST</code>控制空闲变量则将会造成比较大的影响。对于<code>SIZESORT-</code>的情况来说,由于未分配的大块内存一直在最前面,因此很容易在反复删除小内存段的过程中不断积累内存碎片;优点是每次直接扫描的第一个就将是可以直接使用的内存,搜索的次数明显减少。采用<code>SIZESORT+</code>的情况下,由于小碎片都积累在前面,因此如果此时遇到比较大块的内存需要分配时,则会增加需要搜索的内存段数量,时间会增加;优点是每次分配的时候都尽量先匹配比较小的内存段,对于多次分配小内存的情况来说,不会那么容易产生浪费。</p></li><li><p>首先,如果对内存段不采用合并处理的话,随着时间推移,内存碎片将会越来越多,并且后面能够成功分配内存需要的搜索次数以及成功概率都会升高。其次,由于内存合并是在地址连续的基础上才会进行合并,因此如果采用了<code>SIZESORT</code>的分配方法,打乱了地址的顺序,虽然依旧有成功合并的可能,但是效率还是要低于以<code>ADDRSORT</code>方法对空闲内存段进行排序。</p></li><li><p>如果<code>-P</code>设置的太高,模拟的则是在平时写程序的过程中一直不对申请的内存空间进行<code>Free</code>释放,则会导致空间过早的就被使用完毕,后续无法再继续分配,设置的低的情况下则代表了内存每次使用分配完毕以后都立即释放,这样就能够一直有空闲的内存等待分配。</p></li><li><p>如果要生成高度碎片化的空间,只要让空闲的空间碎片始终不被合并,并且分配的时候根据上述中第四题得到的规律增加搜索次数,让即使被<code>Free</code>的内存块也无法被使用或增加使用次数即可。</p></li></ol>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>qBittorrent与jellyfin搭建自动追番引导</title>
<link href="/p/8d2011c6.html"/>
<url>/p/8d2011c6.html</url>
<content type="html"><![CDATA[<h2 id="环境"><a class="markdownIt-Anchor" href="#环境"></a> 环境</h2><ul><li>Docker Engine: <code>20.10.15</code></li><li>Ubuntu: <code>20.04.4 LTS</code></li><li>X86平台</li></ul><h2 id="部署教程"><a class="markdownIt-Anchor" href="#部署教程"></a> 部署教程</h2><p>部署使用的是老电脑上的<code>Ubuntu 20.04.4 LTS</code>,为了便于备份配置以及轻量上手,采用了<code>Docker-Compose</code>的一件式部署方式,该方案主要倾向解决追番问题,目前基本解决刮削问题。</p><h3 id="docker安装"><a class="markdownIt-Anchor" href="#docker安装"></a> Docker安装</h3><p>请在百度等搜索引擎直接搜索对应自己平台的"Docker 安装 教程"</p><h3 id="docker-compose部署"><a class="markdownIt-Anchor" href="#docker-compose部署"></a> Docker-Compose部署</h3><p>推荐的<code>qBittorrent</code>+<code>Jellyfin</code>部署配置文件如下</p><figure class="highlight yaml"><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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span><br><span class="hljs-attr">services:</span><br> <span class="hljs-attr">jellyfin:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">nyanmisaka/jellyfin:latest</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">container_name:</span> <span class="hljs-string">jellyfin</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">$PWD/conf/jellyfin:/config</span> <span class="hljs-comment"># 对应Jellyfin的配置文件</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">$PWD/cache:/cache</span> <span class="hljs-comment"># 对应Jellyfin的缓存文件</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">$PWD/downloads/media:/media</span> <span class="hljs-comment"># 对应Jellyfin的媒体文件夹</span><br> <span class="hljs-attr">ports:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">"8096:8096"</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">TZ=Asia/Shanghai</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">UID=1000</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">GID=1000</span><br> <span class="hljs-attr">devices:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">/dev/dri:/dev/dri</span> <span class="hljs-comment"># 如果要使用硬解配置</span><br><br> <span class="hljs-attr">qbittorrent:</span><br> <span class="hljs-attr">image:</span> <span class="hljs-string">johngong/qbittorrent:latest</span><br> <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span><br> <span class="hljs-attr">hostname:</span> <span class="hljs-string">qbittorrent</span><br> <span class="hljs-attr">container_name:</span> <span class="hljs-string">qbittorrent</span><br> <span class="hljs-attr">volumes:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">$PWD/conf/qbit:/config</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">$PWD/downloads:/Downloads</span><br> <span class="hljs-attr">network_mode:</span> <span class="hljs-string">"host"</span><br> <span class="hljs-attr">environment:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">UID=0</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">GID=0</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">TRACKERSAUTO=YES</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">WEBUIPORT=8995</span> <span class="hljs-comment"># 网页端口</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">TRACKERS_LIST_URL=https://cdn.jsdelivr.net/gh/ngosang/trackerslist@master/trackers_all.txt</span> <span class="hljs-comment"># 自动更新种子文件</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">UMASK=022</span><br></code></pre></td></tr></table></figure><blockquote><p>如果出现了qBittorrent配置有可能随着更新有变化,如果使用过程中出现问题,或需要自己额外配置,具体参考<a href="https://hub.docker.com/r/johngong/qbittorrent">johngong/qbittorrent</a>内的介绍进行修改即可。</p></blockquote><h3 id="配置自动改名工具"><a class="markdownIt-Anchor" href="#配置自动改名工具"></a> 配置自动改名工具</h3><p>下载 <a href="https://github.com/Nriver/Episode-ReName/files/8644661/EpisodeReName.zip">EpisodeReName.zip</a> 并且解压在<code>qbittorrent</code>挂载的<code>Downloads</code>目录下,用于下文中设置自动改名</p><h2 id="使用效果"><a class="markdownIt-Anchor" href="#使用效果"></a> 使用效果</h2><h3 id="rss订阅"><a class="markdownIt-Anchor" href="#rss订阅"></a> RSS订阅</h3><ol><li><p>在诸如<a href="https://mikanani.me/?ref=www.myiys.com">蜜柑计划</a>的网站,找到自己想要看的番剧或电视剧对应的<code>RSS</code>链接</p></li><li><p>在<code>qbittorrent</code>当中添加RSS规则,示例如下</p><p><img src="https://lsky.halc.top/uPhEeN.png" alt="RSS订阅" /></p></li><li><p>添加完毕RSS规则以后,则需要设置下载路径。由于<code>Jellyfin</code>刮削为识别文件夹名字进行刮削,因此这里的命名必须要符合规范来提高成功率</p><p><img src="https://lsky.halc.top/9FKNvM.png" alt="下载路径设置" /><br />注意文件夹命名要为"番剧名/S+季度数"即可</p></li><li><p>在<code>qbittorrent</code>设置内开启自动下载,后续只要识别到了RSS更新,就能自动下载到目标文件夹下</p></li></ol><h3 id="自动修改剧集名"><a class="markdownIt-Anchor" href="#自动修改剧集名"></a> 自动修改剧集名</h3><p>自动修改剧集名字使用的为<a href="https://github.com/Nriver/Episode-ReName">Episode-ReName</a>工具</p><ol><li><p>下载并将<code>Episode-ReName</code>放于<code>Docker</code>挂载后的<code>downloads</code>目录下</p></li><li><p>配置下载完毕自动运行<code>EpisodeRename</code>来对番剧重命名</p><p><img src="https://lsky.halc.top/D99DtN.png" alt="自动重命名" /></p><p>配置参数如下</p> <figure class="highlight sh"><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><code class="hljs sh">/Downloads/EpisodeReName <span class="hljs-string">"%D/%N"</span> 10 <br><span class="hljs-comment"># 10指下载完毕10s后执行</span><br><span class="hljs-comment"># "%D/%N"指对下载完毕后单文件执行</span><br></code></pre></td></tr></table></figure></li><li><p>由于重命名后的文件无法直接继续做种,因此在<code>qbittorrent</code>内同样要设置自动取消做种上传</p><p><img src="https://lsky.halc.top/OGxcUV.png" alt="取消做种" /></p><p><strong>秉承BT分享的精神,或者使用PT站的朋友可以学习如何设置硬链接等方式对文件实现共享,本文不做解释</strong></p></li></ol><h3 id="jellyfin元数据插件"><a class="markdownIt-Anchor" href="#jellyfin元数据插件"></a> Jellyfin元数据插件</h3><p>元数据刮削主要用的是<code>TheMovieDb</code>, <code>AniDB</code>, <code>AniSearch</code>和<code>AniList</code>这几个插件,不过依旧存在抓取的时候抓到英文介绍为多的问题,不过暂且算是能使用,海报和宣传图也基本上都有,日常使用没有很大问题</p>]]></content>
<categories>
<category>安装引导</category>
</categories>
<tags>
<tag>qbittorrent</tag>
<tag>jellyfin</tag>
<tag>docker</tag>
</tags>
</entry>
<entry>
<title>WSL配置Proxy代理引导</title>
<link href="/p/6088c65c.html"/>
<url>/p/6088c65c.html</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>在三番五次被<code>wsl</code>的<code>proxy</code>问题折腾的心态爆炸,并重装了好几次系统以后,总算理清楚了<code>WSL</code>如果想要搭配<code>windowns</code>上的<code>clash for windows</code>的正确使用方法。把之前无论是需要脚本还是各种复杂操作的博客都删了,在这里记录一个完全不需要任何脚本,也不需要额外配置防火墙的合理方案。</p><h2 id="工具环境"><a class="markdownIt-Anchor" href="#工具环境"></a> 工具环境</h2><ul><li><code>WSL 2 ArchLinux</code>(理论上来说其他发行版应该相同)</li><li><code>Windows 11</code>(win10应该同理)</li><li><code>Clash For Windows</code></li></ul><h2 id="clash的配置"><a class="markdownIt-Anchor" href="#clash的配置"></a> Clash的配置</h2><blockquote><p>改方案为<code>WSL</code>继承<code>System Proxy</code>来达到代理上网的目的,使用<code>TUN Mode</code>直接用就行,不需要额外设置</p></blockquote><ol><li><p>正常配置好<code>Clash For Windows</code>,并且启用<code>Allow LAN</code>的设置</p></li><li><p>在<code>允许应用或功能通过 Windows Defender防火墙</code>中寻找是否有<code>clash-win64.exe</code>的规则配置,注意不是 <strong>Clash For Windows</strong> ,<code>CFW</code>本身只是<code>clash</code>的一个前端,在启动<code>CFW</code>的时候有概率防火墙只添加<code>CFW</code>本身,而不添加作为核心的<code>clash</code>的防火墙规则,这个时候则需要我们手动修改</p></li><li><p>如果已经有了<code>clash-win64.exe</code>的规则,则只需要配置<strong>专有和公共网络同时允许</strong>即可。如果没有<code>clash-win64.exe</code>的规则,可以通过下方的<code>允许其他应用</code>手动添加规则,具体<code>clash</code>核心文件的路径可以通过任务管理器后台或<code>Clash for Windows\resources\static\files\win\x64\clash-win64.exe</code>类似的路径查询到。添加规则的时候同时允许<strong>专用和公共</strong>即可</p></li></ol><blockquote><p>之前经常折腾好了防火墙但过了三四个月或者一段时间后<code>wsl</code>和<code>windows</code>之间就因为防火墙断开,但总找不到原因,现在想想很有可能是当时<code>clash for windows</code>升级安装的时候规则被覆盖或路径变化导致的。WSL2的网络对Windows来说也是一个Public的公开网络,在设置了单独程序允许通信之后,虽然<code>wsl</code>有可能无法<code>ping</code>通<code>windows</code>的主机,但正常访问<code>clash</code>的代理端口是没有问题的</p></blockquote><h2 id="wsl的配置"><a class="markdownIt-Anchor" href="#wsl的配置"></a> WSL的配置</h2><p>到这里为止防火墙的问题就解决了,只需要通过合理的方法配置好<code>WSL</code>下的代理变量就可以正常使用。其中<code>主机名.local</code>这个域名是会直接在<code>wsl</code>内映射到作为<code>dns</code>服务器的宿主机上,因此并不需要写额外的脚本来添加映射</p><p>较为简单的方法即通过<code>zsh</code>终端下<code>oh my zsh</code>+ <a href="https://github.com/SukkaW/zsh-proxy">zsh-proxy</a> 插件,通过设置<code>proxy</code>来实现全局基本功能的代理配置,而在<code>config_proxy</code>步骤中的代理IP填入类似<code>Zephyrus.local:7890</code>格式的地址即可。</p><blockquote><p>Zephyrus为我Windows的设备名,可在Windows设置中重命名,一般来说默认设置应该为类似<code>DESKTOP-XXXX.local:7890</code>,修改和查看的方法可通过搜索引擎自己解决</p></blockquote><p>通过以上方式配置后的<code>WSL</code>就可以正常通过<code>Windows</code>上的<code>Clash</code>代理了。每次<code>WSL</code>出网络问题总是感觉莫名其妙没头绪,之前也试过通过<code>New-NetFireWallRule</code>一类的方法放行防火墙,但都不是很好用或者后面偶尔突然就出问题,现在总算弄清楚了原因而且能很舒服的使用<code>win</code>里面的代理了。</p>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>zsh</tag>
<tag>WSL</tag>
<tag>Clash</tag>
</tags>
</entry>
<entry>
<title>OSTEP:通过分段管理内存</title>
<link href="/p/783d8b13.html"/>
<url>/p/783d8b13.html</url>
<content type="html"><![CDATA[<h2 id="第十六章分段"><a class="markdownIt-Anchor" href="#第十六章分段"></a> 第十六章:分段</h2><ol><li><p>这里记录一个样例作为例子,其他的答案则跳过重复的计算步骤</p><p>运行第一个<code>seed</code>可以得到以下输出</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></pre></td><td class="code"><pre><code class="hljs log">ARG seed 0<br>ARG address space size 128<br>ARG phys mem size 512<br><br>Segment register information:<br><br>Segment 0 base (grows positive) : 0x00000000 (decimal 0)<br>Segment 0 limit : 20<br><br>Segment 1 base (grows negative) : 0x00000200 (decimal 512)<br>Segment 1 limit : 20<br><br>Virtual Address Trace<br>VA 0: 0x0000006c (decimal: 108) --> PA or segmentation violation?<br>VA 1: 0x00000061 (decimal: 97) --> PA or segmentation violation?<br>VA 2: 0x00000035 (decimal: 53) --> PA or segmentation violation?<br>VA 3: 0x00000021 (decimal: 33) --> PA or segmentation violation?<br>VA 4: 0x00000041 (decimal: 65) --> PA or segmentation violation?<br></code></pre></td></tr></table></figure><p>对于<code>VA 0: 0x0000006c (decimal: 108)</code>的情况计算如下</p><ol><li>由<code>Address Size</code>为128<code>(2^7)</code>得到高位为第7位</li><li>将<code>VA</code>的<code>0x6c(108)</code>转为二进制,按7位来算则是<code>1 101100</code>,因此可以知道这是<code>SEG 1</code>的地址</li><li>栈是从最下方的内存反方向增加,<code>VA</code>最底部内存位为<code>0x80(128)</code>,因此<code>0x6c(108)</code>的<code>VA</code>对应偏移量为<code>108 - 128 = -20</code></li><li><code>VA</code>中<code>0x80(128)</code>的地址对应PA为<code>0x200(512)</code>,因此按偏移量<code>-20</code>算可以得到VA中<code>0x6c(108)</code>对应的<code>PA</code>为<code>0x1ec(492)</code></li></ol><p><img src="https://lsky.halc.top/qm2dvL.jpg" alt="计算过程(字丑.jpg)" /></p> <details> <summary>答案</summary> <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><code class="hljs log">Virtual Address Trace<br>VA 0: 0x0000006c (decimal: 108) --> VALID in SEG1: 0x000001ec (decimal: 492)<br>VA 1: 0x00000061 (decimal: 97) --> SEGMENTATION VIOLATION (SEG1)<br>VA 2: 0x00000035 (decimal: 53) --> SEGMENTATION VIOLATION (SEG0)<br>VA 3: 0x00000021 (decimal: 33) --> SEGMENTATION VIOLATION (SEG0)<br>VA 4: 0x00000041 (decimal: 65) --> SEGMENTATION VIOLATION (SEG1)<br></code></pre></td></tr></table></figure> </details></li><li><p>以第一题为参考;在虚拟地址中<code>SEG 0</code>的范围是<code>0-19</code>,<code>SEG 1</code>的范围是<code>108-127</code>,非法地址为<code>20-107</code>。</p><p>以下为通过运行<code>-A</code>标志对分界点进行分别测试有如下结果</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></pre></td><td class="code"><pre><code class="hljs log">Virtual Address Trace<br> VA 0: 0x00000000 (decimal: 0) --> VALID in SEG0: 0x00000000 (decimal: 0)<br> VA 1: 0x00000013 (decimal: 19) --> VALID in SEG0: 0x00000013 (decimal: 19)<br> VA 2: 0x00000014 (decimal: 20) --> SEGMENTATION VIOLATION (SEG0)<br> VA 3: 0x0000006b (decimal: 107) --> SEGMENTATION VIOLATION (SEG1)<br> VA 4: 0x0000006c (decimal: 108) --> VALID in SEG1: 0x000001ec (decimal: 492)<br> VA 5: 0x0000007f (decimal: 127) --> VALID in SEG1: 0x000001ff (decimal: 511)<br></code></pre></td></tr></table></figure></li><li><p>设置为以下指令即可</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">./segmentation.py -a 16 -p 128 -A 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 --b0 0 --l0 2 --b1 16 --l1 1 -c<br></code></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></pre></td><td class="code"><pre><code class="hljs log">Virtual Address Trace<br> VA 0: 0x00000000 (decimal: 0) --> VALID in SEG0: 0x00000000 (decimal: 0)<br> VA 1: 0x00000001 (decimal: 1) --> VALID in SEG0: 0x00000001 (decimal: 1)<br> VA 2: 0x00000002 (decimal: 2) --> SEGMENTATION VIOLATION (SEG0)<br> VA 3: 0x00000003 (decimal: 3) --> SEGMENTATION VIOLATION (SEG0)<br> VA 4: 0x00000004 (decimal: 4) --> SEGMENTATION VIOLATION (SEG0)<br> VA 5: 0x00000005 (decimal: 5) --> SEGMENTATION VIOLATION (SEG0)<br> VA 6: 0x00000006 (decimal: 6) --> SEGMENTATION VIOLATION (SEG0)<br> VA 7: 0x00000007 (decimal: 7) --> SEGMENTATION VIOLATION (SEG0)<br> VA 8: 0x00000008 (decimal: 8) --> SEGMENTATION VIOLATION (SEG1)<br> VA 9: 0x00000009 (decimal: 9) --> SEGMENTATION VIOLATION (SEG1)<br> VA 10: 0x0000000a (decimal: 10) --> SEGMENTATION VIOLATION (SEG1)<br> VA 11: 0x0000000b (decimal: 11) --> SEGMENTATION VIOLATION (SEG1)<br> VA 12: 0x0000000c (decimal: 12) --> SEGMENTATION VIOLATION (SEG1)<br> VA 13: 0x0000000d (decimal: 13) --> SEGMENTATION VIOLATION (SEG1)<br> VA 14: 0x0000000e (decimal: 14) --> SEGMENTATION VIOLATION (SEG1)<br> VA 15: 0x0000000f (decimal: 15) --> VALID in SEG1: 0x0000000f (decimal: 15)<br></code></pre></td></tr></table></figure></li><li><p>要让90%的地址可以被访问,则对于<code>SEG 0</code>的界限寄存器到<code>SEG 1</code>的界限寄存器中间地址差要为总虚拟地址的<code>10%</code>即可。</p><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mrow><mo stretchy="false">(</mo><mi>b</mi><mn>1</mn><mo>−</mo><mi>l</mi><mn>1</mn><mo>−</mo><mo stretchy="false">(</mo><mi>b</mi><mn>0</mn><mo>+</mo><mi>l</mi><mn>0</mn><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow><mrow><mi>A</mi><mi>d</mi><mi>d</mi><mi>r</mi><mi>e</mi><mi>s</mi><mi>s</mi><mi>S</mi><mi>i</mi><mi>z</mi><mi>e</mi></mrow></mfrac><mo><</mo><mn>10</mn><mi mathvariant="normal">%</mi></mrow><annotation encoding="application/x-tex"> \frac{(b1 - l1 - (b0 + l0))}{Address Size} < 10\%</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.113em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">A</span><span class="mord mathdefault">d</span><span class="mord mathdefault">d</span><span class="mord mathdefault" style="margin-right:0.02778em;">r</span><span class="mord mathdefault">e</span><span class="mord mathdefault">s</span><span class="mord mathdefault">s</span><span class="mord mathdefault" style="margin-right:0.05764em;">S</span><span class="mord mathdefault">i</span><span class="mord mathdefault" style="margin-right:0.04398em;">z</span><span class="mord mathdefault">e</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord mathdefault">b</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault" style="margin-right:0.01968em;">l</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mopen">(</span><span class="mord mathdefault">b</span><span class="mord">0</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathdefault" style="margin-right:0.01968em;">l</span><span class="mord">0</span><span class="mclose">)</span><span class="mclose">)</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel"><</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.80556em;vertical-align:-0.05556em;"></span><span class="mord">1</span><span class="mord">0</span><span class="mord">%</span></span></span></span></span></p></li><li><p>所以地址都失效代表没有可以访问的地址,因此<code>l0</code>和<code>l1</code>界限寄存器都设置为0即可</p></li></ol>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>OSTEP:虚拟内存和物理内存地址转换</title>
<link href="/p/1724953e.html"/>
<url>/p/1724953e.html</url>
<content type="html"><![CDATA[<h2 id="第十五章机制地址转换"><a class="markdownIt-Anchor" href="#第十五章机制地址转换"></a> 第十五章:机制:地址转换</h2><p>该章节主要引出了基址寄存器和界限寄存器的概念,表述了在操作系统内程序执行的时候虚拟内存的分布和物理内存的地址转换关系</p><ol><li><p>判断是否越界只需要将访问内存地址大小和<code>Limit</code>进行比较,在小于<code>Limit</code>的情况下直接做加法即可</p></li><li><p>获取到所有访问的数据后,可以发现访问的地址中最大的为</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs log">VA 9: 0x000003a1 (decimal: 929)<br></code></pre></td></tr></table></figure><p>因此只需要将<code>-l</code>设置为930即可</p><blockquote><p>补充为什么不是929</p><p>界限寄存器是在基址寄存器的基础上开始<code>limit</code>个单位的内存可被使用,就好比数组中的<code>limit</code>为10个空间的情况下,只有<code>0-9</code>可以被使用。对应到该题,如果想要让<code>(0x000003a1)</code>的地址不越界,则需要设置<code>930</code>个可用空间</p></blockquote></li><li><p>题目默认的物理地址为<code>16k</code>,也就是<code>16,384</code>,只需要设置<code>-b</code>为<code>16384 - 100 = 16284</code>即可</p></li><li><p>和上文实验差别不大,其中<code>-a</code>参数是设置随即生成的取地址的大小范围,<code>-p</code>为设置物理内存的最大范围,实际内存主要是否越界只和<code>-b</code>的基址寄存器、<code>-l</code>的界限寄存器以及<code>-p</code>的物理内存有关系,<code>-a</code>作为一个随机数范围没有实际含义</p></li><li><p>通过<code>PyPlot</code>画图,在默认的<code>-n</code>情况下,当<code>limit</code>从0逐渐增加到<code>asize</code>的时候,可以得到大致如下的图片</p><p><img src="https://lsky.halc.top/PehmA1.png" alt="limit" /></p><blockquote><p>附画图代码</p></blockquote> <figure class="highlight python"><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">import</span> re<br><span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">execRelocation</span>(<span class="hljs-params">seed, limit, num</span>):<br> r = os.popen(<span class="hljs-string">'python3 relocation.py -s %d -l %d -c'</span> % (seed, limit))<br> pass_num = r.read().count(<span class="hljs-string">"VALID"</span>)<br> r.close()<br> <span class="hljs-keyword">return</span> pass_num / num<br><br><span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:<br> limitTop = <span class="hljs-number">1024</span><br> limit_list = []<br> case_list = []<br> <span class="hljs-comment"># 从 0 - limitTop开始以50为公差测试对应通过的概率</span><br> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">0</span>, limitTop, <span class="hljs-number">50</span>):<br> <span class="hljs-built_in">sum</span> = <span class="hljs-number">0</span><br> <span class="hljs-comment"># 对于每个limit重复实验20次,取平均值</span><br> <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">0</span>, <span class="hljs-number">20</span>):<br> <span class="hljs-built_in">sum</span> += execRelocation(j, i, <span class="hljs-number">10</span>)<br> <span class="hljs-comment"># 结果存入list</span><br> case_list.append(<span class="hljs-built_in">sum</span> / <span class="hljs-number">10</span>)<br> limit_list.append(i)<br> <span class="hljs-comment"># 画图</span><br> plt.xlabel(<span class="hljs-string">"limit"</span>)<br> plt.ylabel(<span class="hljs-string">"pass rate"</span>)<br> plt.plot(limit_list, case_list)<br> plt.savefig(<span class="hljs-string">"limit.png"</span>)<br></code></pre></td></tr></table></figure></li></ol>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>修复WSL中env:'bash\r'的冲突问题</title>
<link href="/p/b3a8b5ef.html"/>
<url>/p/b3a8b5ef.html</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>由于之前C/C++环境配置出问题,近期把<code>WSL</code>重装了一次,结果在配置<code>zsh-proxy</code>的时候出现了报错</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs error">/usr/bin/env: ‘bash\r’: No such file or directory<br></code></pre></td></tr></table></figure><p>直接搜索的方案要么是直接屏蔽掉Windows的<code>Path</code>继承,要么是说重启下<code>wsl</code>就好,但都比较模棱两可</p><h2 id="问题原因"><a class="markdownIt-Anchor" href="#问题原因"></a> 问题原因</h2><p>后面在查看了<code>proxy</code>相关的报错后,发现只有配置<code>git</code>的<code>proxy</code>时会出现这个问题,同时根据<code>Stack Overflow</code>上别人类似情况的提问,发现<code>npm</code>同样也无法使用。检查后发现是在<code>Windows</code>对应的<code>Path</code>内,<code>git</code>和<code>npm</code>本身没有<code>.exe</code>的后缀就能启动,而<code>wsl</code>内是可以执行<code>Windows</code>下的部分可执行文件的,因此<code>wsl</code>调用了基于<code>Windows</code>的环境变量,从而导致了换行符与<code>wsl</code>的<code>linux</code>格式不兼容。</p><h2 id="解决方案"><a class="markdownIt-Anchor" href="#解决方案"></a> 解决方案</h2><p>本来想的是怎么处理屏蔽<code>Windows</code>的相关<code>Path</code>,后面发现只需要在WSL上重新安装好<code>git</code>和<code>npm</code>后重启<code>wsl</code></p><figure class="highlight ps"><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><code class="hljs ps">wsl <span class="hljs-literal">--shudown</span><br><span class="hljs-comment"># 或者仅关闭指定发行版,例如`Arch`</span><br>wsl <span class="hljs-literal">-t</span> Arch<br></code></pre></td></tr></table></figure><p>就可以解决问题了。之前在<code>Stack Overflow</code>上其他人能直接通过重启<code>wsl</code>解决问题,应该也是无意中自己已经覆盖安装过了对应的工具,然后重启才取得了效果,在这里做个记录以做备忘。</p>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>WSL</tag>
</tags>
</entry>
<entry>
<title>使用Github Actions自动部署Hexo</title>
<link href="/p/7bfa5e14.html"/>
<url>/p/7bfa5e14.html</url>
<content type="html"><![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>之前博客一直用的都是<code>Jekyll</code>框架,在使用<code>Github Pages</code>进行部署的时候并不需要自己手动配置,不过在换了<code>Hexo</code>主题之后,每次写完了博客除了要<code>push</code>一次<code>commit</code>到博客的内容分支上,还需要自己手动<code>deploy</code>一次。虽然也不会很麻烦,不过用<code>Github Actions</code>来完成这个过程也要更顺畅一些。原本觉得这个需求应该很简单,直接在<code>Actions</code>上执行一次<code>hexo g -d</code>的指令就好,结果因为<code>Github</code>在<code>HTTPS</code>上对<code>Token</code>的验证,以及<code>Hexo</code>自带的<code>one-command-deployment</code>存在<a href="https://github.com/hexojs/hexo/issues/4757#issuecomment-901613964">BUG</a>,折腾到凌晨两三点才发现,并且通过<code>issues</code>里面提供的修改链接使用<code>oauth</code>进行<code>url</code>验证的方法还是失败了,最后花了半小时改成了<code>ssh</code>密钥验证很轻松就完成了。。下面为对<code>Hexo</code>的<code>Actions</code>的脚本的一个备份</p><h2 id="脚本备份"><a class="markdownIt-Anchor" href="#脚本备份"></a> 脚本备份</h2><p>配置文件</p><figure class="highlight yaml"><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></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Hexo</span> <span class="hljs-string">Deploy</span><br><br><span class="hljs-attr">on:</span><br> <span class="hljs-attr">push:</span><br> <span class="hljs-attr">branches:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-string">hexo</span><br><br><span class="hljs-attr">jobs:</span><br> <span class="hljs-attr">deploy:</span><br> <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span><br> <span class="hljs-attr">steps:</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">ref:</span> <span class="hljs-string">hexo</span><br><br> <span class="hljs-comment"># Caching dependencies to speed up workflows. (GitHub will remove any cache entries that have not been accessed in over 7 days.)</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Cache</span> <span class="hljs-string">node</span> <span class="hljs-string">modules</span><br> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/cache@v1</span><br> <span class="hljs-attr">id:</span> <span class="hljs-string">cache</span><br> <span class="hljs-attr">with:</span><br> <span class="hljs-attr">path:</span> <span class="hljs-string">node_modules</span><br> <span class="hljs-attr">key:</span> <span class="hljs-string">${{</span> <span class="hljs-string">runner.os</span> <span class="hljs-string">}}-node-v2-${{</span> <span class="hljs-string">hashFiles('**/package-lock.json')</span> <span class="hljs-string">}}</span><br> <span class="hljs-attr">restore-keys:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> ${{ runner.os }}-node-v2</span><br><span class="hljs-string"></span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">Dependencies</span><br> <span class="hljs-attr">if:</span> <span class="hljs-string">steps.cache.outputs.cache-hit</span> <span class="hljs-type">!=</span> <span class="hljs-string">'true'</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> npm ci</span><br><span class="hljs-string"></span><br> <span class="hljs-comment"># Deploy hexo blog website.</span><br> <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">and</span> <span class="hljs-string">Deploy</span><br> <span class="hljs-attr">run:</span> <span class="hljs-string">|</span><br><span class="hljs-string"> mkdir -p ~/.ssh/</span><br><span class="hljs-string"> echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_rsa</span><br><span class="hljs-string"> chmod 600 ~/.ssh/id_rsa</span><br><span class="hljs-string"> ssh-keyscan github.com >> ~/.ssh/known_hosts</span><br><span class="hljs-string"> git config --global user.email "[email protected]"</span><br><span class="hljs-string"> git config --global user.name "HalcyonAzure"</span><br><span class="hljs-string"> npx hexo g -d</span><br><span class="hljs-string"> rm -rf ~/.ssh/id_rsa</span><br></code></pre></td></tr></table></figure><p>整个脚本大致流程为</p><ol><li><p>检测到<code>hexo</code>的内容分支有<code>push</code>之后,<code>checkout</code>到内容分支。</p></li><li><p>判断是否有<code>nodejs</code>的<code>modules</code>缓存。</p><ul><li>如果检测到有效缓存则跳过安装步骤,直接进行下一步。</li><li>如果没有检测到有效缓存则对模块进行部署</li></ul></li><li><p>创建<code>id_rsa</code>密钥文件,并将仓库中<code>DEPLOY_KEY</code>的<code>secret</code>写入密钥文件,并且配置<code>github.com</code>的信任和全局帐号邮箱</p></li><li><p>对<code>hexo</code>进行<code>generate & deploy</code>操作</p></li><li><p>删除写入的密钥文件</p></li></ol>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>hexo</tag>
</tags>
</entry>
<entry>
<title>解决Windows未使用端口被占用问题</title>
<link href="/p/22453a61.html"/>
<url>/p/22453a61.html</url>
<content type="html"><![CDATA[<h2 id="参考文章"><a class="markdownIt-Anchor" href="#参考文章"></a> 参考文章</h2><ol><li><a href="https://stackoverflow.com/questions/52212012/hortonworks-docker-sandbox-environment-cannot-start/">Hortonworks Docker Sandbox environment cannot start</a></li><li><a href="https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/default-dynamic-port-range-tcpip-chang">default-dynamic-port-range-tcpip-chang</a></li></ol><h2 id="问题产生"><a class="markdownIt-Anchor" href="#问题产生"></a> 问题产生</h2><p>在无脑跟着网上教材开启Windows的<code>SandBox</code>的时候开启了Hyper-V的功能,结果尝试在<code>6800</code>端口运行和往常一样的<code>Aria2</code>的<code>Docker</code>容器的时候出现了端口报错的情况,通过<code>netstat</code>排查也没发现<code>6800</code>端口被占用了,后面发现应该是Windows的动态端口在开了<code>Hyper-V</code>之后被修改了</p><p>通过以下指令可以分别查看<code>ipv4/ipv6</code>的<code>tcp/udp</code>起始端口</p><figure class="highlight ps"><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><code class="hljs ps">netsh int ipv4 show dynamicport tcp<br>netsh int ipv4 show dynamicport udp<br>netsh int ipv6 show dynamicport tcp<br>netsh int ipv6 show dynamicport udp<br></code></pre></td></tr></table></figure><p>在我的情况下,起始端口从原本默认的<code>49152</code>被修改成了从<code>1024</code>开始,因此<code>6800</code>端口无法使用</p><h2 id="问题解决"><a class="markdownIt-Anchor" href="#问题解决"></a> 问题解决</h2><p>在参考问题中找到了对应的解决方案</p><p>如果需要继续使用<code>windows Virtual platform form windows feature</code>(不确定这里是不是指Hyper-V,所以不翻译了)则</p><ol><li><p>关闭Windows服务上对应的功能,关闭后系统会请求重启</p></li><li><p>通过以下指令修改动态起始端口 <s>(<code>49152</code>是Windows默认设置)</s> 在使用<code>adb</code>连<code>WSA</code>的调试时,发现默认端口为<code>58526</code>,所以还是用<code>100000</code>把</p><figure class="highlight ps"><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><code class="hljs ps">netsh int ipv4 <span class="hljs-built_in">set</span> dynamicport tcp <span class="hljs-built_in">start</span>=<span class="hljs-number">64536</span> num=<span class="hljs-number">1000</span><br>netsh int ipv4 <span class="hljs-built_in">set</span> dynamicport udp <span class="hljs-built_in">start</span>=<span class="hljs-number">64536</span> num=<span class="hljs-number">1000</span><br>netsh int ipv6 <span class="hljs-built_in">set</span> dynamicport tcp <span class="hljs-built_in">start</span>=<span class="hljs-number">64536</span> num=<span class="hljs-number">1000</span><br>netsh int ipv6 <span class="hljs-built_in">set</span> dynamicport udp <span class="hljs-built_in">start</span>=<span class="hljs-number">64536</span> num=<span class="hljs-number">1000</span><br></code></pre></td></tr></table></figure></li><li><p>重新启用对应的功能</p></li></ol><p>如果没有Hyper-V使用需求的情况下,可以尝试直接关闭Hyper-V,然后检查起始端口是否恢复,如果没有恢复的再通过上面的指令手动重新设置起始端口即可</p>]]></content>
<categories>
<category>小技巧</category>
</categories>
<tags>
<tag>Windows</tag>
</tags>
</entry>
<entry>
<title>OSTEP:系统API的调用</title>
<link href="/p/264bf58c.html"/>
<url>/p/264bf58c.html</url>
<content type="html"><![CDATA[<h2 id="第十四章插叙内存操作api"><a class="markdownIt-Anchor" href="#第十四章插叙内存操作api"></a> 第十四章:插叙:内存操作API</h2><ol><li><p>执行<code>null</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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> *pt = <span class="hljs-literal">NULL</span>;<br> <span class="hljs-built_in">free</span>(pt); <span class="hljs-comment">// 释放空指针</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure></li><li><p>在执行完<code>gdb null</code>后的<code>run</code>后,提示了以下信息</p> <details> <summary>log</summary> <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><code class="hljs shell">(gdb) run<br>Starting program: /home/halc/code/cpp/null<br>[Inferior 1 (process 9285) exited normally]<br></code></pre></td></tr></table></figure> </details></li><li><p>在使用<code>valgrind</code>检查后,可以得到以下输出信息</p><details><summary>log</summary><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></pre></td><td class="code"><pre><code class="hljs log">==9579== Memcheck, a memory error detector<br>==9579== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.<br>==9579== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info<br>==9579== Command: ./null<br>==9579==<br>==9579==<br>==9579== HEAP SUMMARY:<br>==9579== in use at exit: 0 bytes in 0 blocks<br>==9579== total heap usage: 0 allocs, 0 frees, 0 bytes allocated<br>==9579==<br>==9579== All heap blocks were freed -- no leaks are possible<br>==9579==<br>==9579== For lists of detected and suppressed errors, rerun with: -s<br>==9579== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)<br></code></pre></td></tr></table></figure> </details><p>根据<a href="https://en.cppreference.com/w/c/memory/free">C/C++ Preference</a>上<code>free()</code>的介绍如下</p><blockquote><p>If is a null pointer, the function does nothing. ptr<br />当指针是空指针的时候,啥都不会发生,因此也理所当然的没有发生任何的内存泄露</p></blockquote></li><li><p>首先使用<code>malloc()</code>函数对内存空间进行分配,但是不使用<code>free()</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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> *pt = (<span class="hljs-type">int</span> *)<span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>));<br> <span class="hljs-comment">// free(pt);</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>使用<code>gdb</code>对可执行文件进行调试如下</p> <details> <summary>gdb log</summary> <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><code class="hljs log">Starting program: /home/halc/code/cpp/null <br>[Inferior 1 (process 9978) exited normally]<br></code></pre></td></tr></table></figure> </details><p>使用<code>valgrind</code>对可执行文件调试入如下</p> <details> <summary>valgrind log</summary> <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></pre></td><td class="code"><pre><code class="hljs log">$ valgrind --leak-check=yes ./null<br>==9930== Memcheck, a memory error detector<br>==9930== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.<br>==9930== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info<br>==9930== Command: ./null<br>==9930==<br>==9930==<br>==9930== HEAP SUMMARY:<br>==9930== in use at exit: 4 bytes in 1 blocks<br>==9930== total heap usage: 1 allocs, 0 frees, 4 bytes allocated<br>==9930==<br>==9930== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1<br>==9930== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)<br>==9930== by 0x10915E: main (null.cpp:6)<br>==9930==<br>==9930== LEAK SUMMARY:<br>==9930== definitely lost: 4 bytes in 1 blocks<br>==9930== indirectly lost: 0 bytes in 0 blocks<br>==9930== possibly lost: 0 bytes in 0 blocks<br>==9930== still reachable: 0 bytes in 0 blocks<br>==9930== suppressed: 0 bytes in 0 blocks<br>==9930==<br>==9930== For lists of detected and suppressed errors, rerun with: -s<br>==9930== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)<br></code></pre></td></tr></table></figure> </details><p>这个时候通过<code>valgrind</code>工具即可检查出,在堆空间中有<code>definitely lost(确定的内存泄露)</code>为<code>4 bytes in 1 blocks</code>,正好对应了代码中<code>sizeof(int)</code>的大小和数量</p></li><li><p>首先使用<code>malloc()</code>函数分配100个<code>int</code>的空间给指针<code>data</code>,然后在<code>data[100]</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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> *data = (<span class="hljs-type">int</span> *)<span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>) * <span class="hljs-number">100</span>);<br> data[<span class="hljs-number">100</span>] = <span class="hljs-number">0</span>;<br> <span class="hljs-built_in">free</span>(data);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>程序直接运行的时候不会报错,没有任何提示。但是使用<code>valgrind</code>进行检查的时候有如下日志</p> <details> <summary>log</summary> <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></pre></td><td class="code"><pre><code class="hljs log">$ valgrind --leak-check=yes ./null<br>==10500== Memcheck, a memory error detector<br>==10500== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.<br>==10500== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info<br>==10500== Command: ./null<br>==10500==<br>==10500== Invalid write of size 4<br>==10500== at 0x10918D: main (null.cpp:7)<br>==10500== Address 0x4a4c1d0 is 0 bytes after a block of size 400 alloc'd<br>==10500== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)<br>==10500== by 0x10917E: main (null.cpp:6)<br>==10500==<br>==10500==<br>==10500== HEAP SUMMARY:<br>==10500== in use at exit: 0 bytes in 0 blocks<br>==10500== total heap usage: 1 allocs, 1 frees, 400 bytes allocated<br>==10500==<br>==10500== All heap blocks were freed -- no leaks are possible<br>==10500==<br>==10500== For lists of detected and suppressed errors, rerun with: -s<br>==10500== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)<br></code></pre></td></tr></table></figure> </details><p>可以看到<code>valgrind</code>成功检测出一个无效的写入,在内存分配之后,只有<code>data[0] - data[99]</code>是可以正常写入的,<code>data[100]</code>并不存在,因此出现了无效写入的错误</p></li><li><p>用和第五题相似的方法创建一个数组,然后通过<code>free()</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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> *data = (<span class="hljs-type">int</span> *)<span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>) * <span class="hljs-number">100</span>);<br> <span class="hljs-built_in">free</span>(data);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, data[<span class="hljs-number">0</span>]);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>在本地电脑上直接运行的时候输出了<code>0</code>,使用<code>valgrind</code>工具进行检查的时候输出了如下日志</p> <details> <summary>log</summary> <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></pre></td><td class="code"><pre><code class="hljs log">==10682== Memcheck, a memory error detector<br>==10682== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.<br>==10682== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info<br>==10682== Command: ./null<br>==10682==<br>==10682== Invalid read of size 4<br>==10682== at 0x1091B3: main (null.cpp:8)<br>==10682== Address 0x4a4c040 is 0 bytes inside a block of size 400 free'd<br>==10682== at 0x483CA3F: free (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)<br>==10682== by 0x1091AE: main (null.cpp:7)<br>==10682== Block was alloc'd at<br>==10682== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)<br>==10682== by 0x10919E: main (null.cpp:6)<br>==10682==<br>0<br>==10682==<br>==10682== HEAP SUMMARY:<br>==10682== in use at exit: 0 bytes in 0 blocks<br>==10682== total heap usage: 2 allocs, 2 frees, 1,424 bytes allocated<br>==10682==<br>==10682== All heap blocks were freed -- no leaks are possible<br>==10682==<br>==10682== For lists of detected and suppressed errors, rerun with: -s<br>==10682== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)<br></code></pre></td></tr></table></figure> </details><p>可以看见检测出了无效的读写<code>Invalid read of size 4</code></p></li><li><p>对<code>data</code>数组中的<code>data[50]</code>进行内存释放,然后输出<code>data[0]</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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> *data = (<span class="hljs-type">int</span> *)<span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>) * <span class="hljs-number">100</span>);<br> <span class="hljs-comment">// free(data);</span><br> <span class="hljs-built_in">free</span>(data + <span class="hljs-number">50</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d\n"</span>, data[<span class="hljs-number">0</span>]);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></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></pre></td><td class="code"><pre><code class="hljs warning">$ ./null<br>free(): invalid pointer<br>[1] 10986 abort ./null<br></code></pre></td></tr></table></figure><p>不需要用<code>valgrind</code>也可以检查出此处有<code>free()</code>的问题</p><blockquote><p>根据 <a href="https://stackoverflow.com/a/20297598">c-free-invalid-pointer</a> 的回答,可以知道</p><p>When you have in fact allocated a block of memory, you can only free it from the pointer returned by. That is to say, only from the beginning of the block.</p><p>当我们分配了一块内存的时候,我们只能从返回的指针开始对这块内存进行释放,也就是说我们只能从内存块的开头对某一处内存进行释放</p></blockquote></li><li><p>通过<code>realloc()</code>函数实现一个类似<code>vector</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><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-type">int</span> *pt = (<span class="hljs-type">int</span> *)<span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>)); <span class="hljs-comment">// 给指针pt分配1个int大小空间的内存</span><br> *pt = <span class="hljs-number">10</span>; <span class="hljs-comment">// 对对应内存位置进行赋值</span><br> pt = (<span class="hljs-type">int</span> *)<span class="hljs-built_in">realloc</span>(pt, <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>) * <span class="hljs-number">2</span>); <span class="hljs-comment">// 将原本的变量扩容为2个int大小空间</span><br> pt[<span class="hljs-number">1</span>] = <span class="hljs-number">20</span>; <span class="hljs-comment">// 对新扩大的内存位置进行赋值</span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d, %d\n"</span>, pt[<span class="hljs-number">0</span>], pt[<span class="hljs-number">1</span>]); <span class="hljs-comment">// 输出pt对应的两个内存位置的结果</span><br> <span class="hljs-built_in">free</span>(pt); <span class="hljs-comment">// 释放堆内存中的pt</span><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>程序编译可以成功通过,使用<code>valgrind</code>也正常通过,没有无效读写错误</p><p>通过这种操作实现的<code>vector</code>可以在访问的时候直接通过<code>index</code>进行访问,时间复杂度为O(1),并且他并不需要和链表一样创建一个指向<code>next</code>的指针,不过在向后添加内容的时候依旧需要O(n)的时间复杂度来向后添加元素。</p><blockquote><p>顺便记录一个Tips, 在C中是允许<code>void *</code>类型进行任意转换的,因此即使没有(int *)也不会出现报错,而在<code>C++</code>中对类型转换的限制更多,并不允许直接进行这样的操作,必须要进行类型转换(通过static_cast转换运算符)才能分配空间给对应的指针。</p></blockquote></li><li><p><s>偷懒,懒得写</s></p></li></ol>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>OSTEP:比例份额的调度策略</title>
<link href="/p/13271c5f.html"/>
<url>/p/13271c5f.html</url>
<content type="html"><![CDATA[<h2 id="第八章调度比例份额"><a class="markdownIt-Anchor" href="#第八章调度比例份额"></a> 第八章:调度:比例份额</h2><p>在CPU资源进行调度的时候,有的时候我们很难让每个程序都尽量公平的分配到资源。“彩票调度<code>(lottery scheduling)</code>”通过给不同的任务分配不同的彩票数,再通过随机数和期望分布来对资源进行调度,实现一个类似于平均分配的调度方法</p><p>本章中文译本内缺少对<code>Linux</code>系统的<code>CFS</code>调度的说明,不过不影响课后练习</p><ol><li><p>完成随机种子1、2和3对应的习题计算</p><blockquote><p>该题目的主要思路即判断随机数取模后的数字和<code>tickets</code>的数量比较,然后依次逐步执行判断即可</p></blockquote></li><li><p>当彩票分布设计的十分极端的情况下,由于第一个<code>Job0 10:1</code>获得的票数太少,几乎不可能在<code>Job1 10:100</code>前完成任务,将会在结果上滞后<code>Job0</code>的完成</p></li><li><p>通过设立不同的<code>seed</code>,可以得到不同的情况如下</p><figure class="highlight sh"><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><code class="hljs sh">$ ./lottery.py -l 100:100,100:100 -s 1 -c<br>...<br>--> JOB 1 DONE at time 196<br>...<br>--> JOB 0 DONE at time 200<br><br>$ ./lottery.py -l 100:100,100:100 -s 2 -c<br>...<br>--> JOB 1 DONE at time 190<br>...<br>--> JOB 0 DONE at time 200<br><br>$ ./lottery.py -l 100:100,100:100 -s 3 -c<br>...<br>--> JOB 0 DONE at time 196<br>...<br>--> JOB 1 DONE at time 200<br><br>$ ./lottery.py -l 100:100,100:100 -s 4 -c<br>...<br>--> JOB 1 DONE at time 199<br>...<br>--> JOB 0 DONE at time 200<br></code></pre></td></tr></table></figure><p>可以大致看出在任务的长度足够大的情况下,调度分布基本公平,最后的结果趋紧于类似RR切换任务的平均期望</p></li><li><p>在修改了<code>quantum</code>大小之后,由于时间片变大,相当于任务本身的长度缩短,整个任务的公平性会偏向不稳定和不公平,大致结果如下</p><figure class="highlight sh"><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><code class="hljs sh">$ ./lottery.py -l 100:100,100:100 -q 20 -c -s 1<br>...<br>--> JOB 0 DONE at time 180<br>...<br>--> JOB 1 DONE at time 200<br><br>$ ./lottery.py -l 100:100,100:100 -q 20 -c -s 2<br>...<br>--> JOB 1 DONE at time 180<br>...<br>--> JOB 0 DONE at time 200<br><br>$ ./lottery.py -l 100:100,100:100 -q 20 -c -s 3<br>...<br>--> JOB 1 DONE at time 120<br>...<br>--> JOB 0 DONE at time 200<br><br>$ ./lottery.py -l 100:100,100:100 -q 20 -c -s 4<br>...<br>--> JOB 1 DONE at time 140<br>...<br>--> JOB 0 DONE at time 200<br></code></pre></td></tr></table></figure></li><li><p>这题主要用来当Python练习了,模拟生成图像的代码如下(和<code>lottery.py</code>实验代码放于同一目录下)</p><figure class="highlight python"><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></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> os<br><span class="hljs-keyword">import</span> re<br><span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt<br><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<br><br><span class="hljs-comment"># 执行彩票概率检查,返回概率结果</span><br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">countLottery</span>(<span class="hljs-params">length, seed</span>):<br> r = os.popen(<br> <span class="hljs-string">"./lottery.py -l "</span> + length + <span class="hljs-string">":100,"</span> + length + <span class="hljs-string">":100 -c"</span> + <span class="hljs-string">" -s "</span> + seed)<br> text = r.read()<br> r.close()<br> lottery_time = re.findall(<span class="hljs-string">r"^--> .*(\d*)"</span>, text, re.M)<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">int</span>(lottery_time[<span class="hljs-number">0</span>])/<span class="hljs-built_in">int</span>(lottery_time[<span class="hljs-number">1</span>])<br><br><span class="hljs-keyword">def</span> <span class="hljs-title function_">average</span>(<span class="hljs-params">length</span>):<br> <span class="hljs-built_in">sum</span> = <span class="hljs-number">0</span><br> <span class="hljs-comment"># 调整重复</span><br> time = <span class="hljs-number">20</span><br> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, time):<br> <span class="hljs-built_in">sum</span> += countLottery(length, <span class="hljs-built_in">str</span>(i))<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">sum</span> / (time - <span class="hljs-number">1</span>)<br><br>length = []<br>chance = []<br><br><span class="hljs-comment"># 设定工作长度和间隔</span><br><br>length_start = <span class="hljs-number">1</span><br>length_end = <span class="hljs-number">100</span><br>step = <span class="hljs-number">5</span><br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> np.arange(length_start, length_end, step):<br> length.append(i)<br> chance.append(average(<span class="hljs-built_in">str</span>(<span class="hljs-built_in">int</span>(i))))<br><br>plt.ylabel(<span class="hljs-string">"Fairness"</span>)<br>plt.xlabel(<span class="hljs-string">"Job Length"</span>)<br><br>plt.plot(length, chance, <span class="hljs-string">'b-'</span>)<br><br>plt.savefig(<span class="hljs-string">"./lottery.png"</span>)<br></code></pre></td></tr></table></figure><p>最后生成的图片效果</p><p><img src="https://lsky.halc.top/ewA3BX.png" alt="模拟图像" /></p></li></ol>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>总结:2022年3月</title>
<link href="/p/18b05b6b.html"/>
<url>/p/18b05b6b.html</url>
<content type="html"><![CDATA[<h2 id="做了的事情"><a class="markdownIt-Anchor" href="#做了的事情"></a> 做了的事情</h2><h3 id="学习方面"><a class="markdownIt-Anchor" href="#学习方面"></a> 学习方面</h3><p>这个月直接写出的可以用的贡献不是很多,主要成就感应该就是来自<code>Python</code>的抓包和发包写一些打卡脚本的目标完成了,实现了学校的打卡自由和机场的签到流量自由。</p><p><code>Rust</code>的学习和<code>CS144</code>的学习在这个月成功被我换成了<code>C++</code>的学习和<code>OSTEP</code>的学习,写lab完成之后带来的快感感觉完全不亚于玩老头环成功挑战Boss后的成就感,<code>OSTEP</code>的学习笔记和答案也有一直在博客里面更新,希望自己这个学期能全部做完加深对操作系统的了解</p><p>这个学期也第一次写了一个能用的<code>repo</code>出来:<a href="https://github.com/HalcyonAzure/lsky-pro-docker">HalcyonAzure/lsky-pro-docker</a>。虽然整个<code>Dockerfile</code>还是抄袭了<code>issues</code>里面别人的劳动成果,但是在调试的过程中遇到的许多问题也加深了自己对<code>container</code>的了解,而且学会了用<code>dev container</code>来做一些临时开发,同时自己写<code>yaml</code>用<code>github actions</code>来自动部署一些项目(白嫖服务器的感觉是真的爽),总的来说成就感可以说是非常强了。</p><p>喔,对了,这个月还把博客重构了一下,改成了用<code>Hexo</code>的<code>Fluid</code>的主题,看着比原来<code>Jekyll</code>的<code>TeXt</code>花里胡哨了不少😉</p><h3 id="生活方面"><a class="markdownIt-Anchor" href="#生活方面"></a> 生活方面</h3><p>这个月由于都忙着<code>Coding</code>,没什么游戏时间。不过让我没想到的是南昌居然也会受到疫情的波及,连续上了近一整个月的网课,伙食方面不能吃烧烤或火锅来释放自己的多巴胺果然感觉还是很难受啊😢,希望能早点解封然后出去吃餐好的</p><p>核酸检测虽然说做的次数也满多,不过学校后面几次安排的时间都蛮合适的,整个流程下来不超过<code>30min</code>,因此也没什么怨言,同时托网课的福,自己也才有了那么多时间能做自己想做的事情,写自己想写的东西,这种无忧无虑不用考虑绩点和平时作业的时候如果多一些就好啦😣</p><h2 id="四月份要做的事情"><a class="markdownIt-Anchor" href="#四月份要做的事情"></a> 四月份要做的事情</h2><h3 id="学习计划"><a class="markdownIt-Anchor" href="#学习计划"></a> 学习计划</h3><ul><li>参加计算机设计大赛,看看能不能摸个奖回来,丰富下经历</li><li>OSTEP的Lab每个礼拜写两篇</li></ul><h3 id="生活计划"><a class="markdownIt-Anchor" href="#生活计划"></a> 生活计划</h3><ul><li>绝对、绝对绝对开始跑步或打排球,调整自己的生活质量</li><li>早睡早起!!!</li><li>水果封校已经不做指望了,就希望能控制住自己的饮食😥</li></ul>]]></content>
<categories>
<category>个人总结</category>
</categories>
<tags>
<tag>总结</tag>
</tags>
</entry>
<entry>
<title>OSTEP:进程的调度策略</title>
<link href="/p/28ea7a49.html"/>
<url>/p/28ea7a49.html</url>
<content type="html"><![CDATA[<h2 id="第七章进程调度介绍"><a class="markdownIt-Anchor" href="#第七章进程调度介绍"></a> 第七章:进程调度/介绍</h2><blockquote><p>参数介绍:</p><p>Response:响应时间,即任务第一次运行的时间</p><p>Turnaround: 完成时刻(周转时间),即任务完成那一刻对应的时间</p><p>Wait: 等待中时间,即任务处于Ready状态,但当前CPU在执行其他任务的等待时间</p></blockquote><ol><li><p>执行结果如下</p><p>FIFO:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs bash">ARG policy FIFO<br>ARG jlist 200,200,200<br><br>Here is the job list, with the run time of each job:<br>Job 0 ( length = 200.0 )<br>Job 1 ( length = 200.0 )<br>Job 2 ( length = 200.0 )<br><br>**Solutions**<br><br>Execution trace:<br>[ time 0 ] Run job 0 <span class="hljs-keyword">for</span> 200.00 secs ( DONE at 200.00 )<br>[ time 200 ] Run job 1 <span class="hljs-keyword">for</span> 200.00 secs ( DONE at 400.00 )<br>[ time 400 ] Run job 2 <span class="hljs-keyword">for</span> 200.00 secs ( DONE at 600.00 )<br><br>Final statistics:<br>Job 0 -- Response: 0.00 Turnaround 200.00 Wait 0.00<br>Job 1 -- Response: 200.00 Turnaround 400.00 Wait 200.00<br>Job 2 -- Response: 400.00 Turnaround 600.00 Wait 400.00<br><br>Average -- Response: 200.00 Turnaround 400.00 Wait 200.00<br></code></pre></td></tr></table></figure><p>同时,对于SJF<code>(Short Job First)</code>,由于每个任务的执行时间相同,所以策略上的处理结果和<code>FIFO</code>相同,不额外列出</p></li><li><p>在按照<code>300</code>,<code>200</code>和<code>100</code>的顺序一次执行任务的时候,对于FIFO策略依次执行,而依据SJF策略,则会先执行时间短的<code>100</code>,依次到最常的<code>300</code>。具体结果如下</p><p>FIFO</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs bash">ARG policy FIFO<br>ARG jlist 300,200,100<br><br>Here is the job list, with the run time of each job:<br>Job 0 ( length = 300.0 )<br>Job 1 ( length = 200.0 )<br>Job 2 ( length = 100.0 )<br><br>**Solutions**<br><br>Execution trace:<br>[ time 0 ] Run job 0 <span class="hljs-keyword">for</span> 300.00 secs ( DONE at 300.00 )<br>[ time 300 ] Run job 1 <span class="hljs-keyword">for</span> 200.00 secs ( DONE at 500.00 )<br>[ time 500 ] Run job 2 <span class="hljs-keyword">for</span> 100.00 secs ( DONE at 600.00 )<br><br>Final statistics:<br>Job 0 -- Response: 0.00 Turnaround 300.00 Wait 0.00<br>Job 1 -- Response: 300.00 Turnaround 500.00 Wait 300.00<br>Job 2 -- Response: 500.00 Turnaround 600.00 Wait 500.00<br><br>Average -- Response: 266.67 Turnaround 466.67 Wait 266.67<br></code></pre></td></tr></table></figure><p>SJF</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs bash">ARG policy SJF<br>ARG jlist 300,200,100<br><br>Here is the job list, with the run time of each job:<br>Job 0 ( length = 300.0 )<br>Job 1 ( length = 200.0 )<br>Job 2 ( length = 100.0 )<br><br>**Solutions**<br><br>Execution trace:<br>[ time 0 ] Run job 2 <span class="hljs-keyword">for</span> 100.00 secs ( DONE at 100.00 )<br>[ time 100 ] Run job 1 <span class="hljs-keyword">for</span> 200.00 secs ( DONE at 300.00 )<br>[ time 300 ] Run job 0 <span class="hljs-keyword">for</span> 300.00 secs ( DONE at 600.00 )<br><br>Final statistics:<br>Job 2 -- Response: 0.00 Turnaround 100.00 Wait 0.00<br>Job 1 -- Response: 100.00 Turnaround 300.00 Wait 100.00<br>Job 0 -- Response: 300.00 Turnaround 600.00 Wait 300.00<br><br>Average -- Response: 133.33 Turnaround 333.33 Wait 133.33<br></code></pre></td></tr></table></figure><p>SJF的好处在于可以先执行时间短的程序,后执行时间长的程序,同时优化了程序的响应、完成和等待时间。缺点在于因为必须先完整的运行某个任务,后执行下一个任务。如果此时需要高频率执行某任务则无能为力(比如高频率的io输出)</p></li><li><p>采用RR策略,时间片设置为1,依次执行<code>10</code>、<code>20</code>和<code>30</code>有以下结果</p><details><summary>RR策略</summary> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><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></pre></td><td class="code"><pre><code class="hljs bash">ARG policy RR<br>ARG jlist 10,20,30<br> <br>Here is the job list, with the run time of each job:<br>Job 0 ( length = 10.0 )<br>Job 1 ( length = 20.0 )<br>Job 2 ( length = 30.0 )<br><br><br>** Solutions **<br><br>Execution trace:<br>[ time 0 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 1 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 2 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 3 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 4 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 5 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 6 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 7 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 8 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 9 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 10 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 11 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 12 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 13 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 14 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 15 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 16 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 17 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 18 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 19 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 20 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 21 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 22 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 23 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 24 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 25 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 26 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 27 ] Run job 0 <span class="hljs-keyword">for</span> 1.00 secs ( DONE at 28.00 )<br>[ time 28 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 29 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 30 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 31 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 32 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 33 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 34 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 35 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 36 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 37 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 38 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 39 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 40 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 41 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 42 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 43 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 44 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 45 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 46 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 47 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 48 ] Run job 1 <span class="hljs-keyword">for</span> 1.00 secs ( DONE at 49.00 )<br>[ time 49 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 50 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 51 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 52 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 53 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 54 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 55 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 56 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 57 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 58 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs<br>[ time 59 ] Run job 2 <span class="hljs-keyword">for</span> 1.00 secs ( DONE at 60.00 )<br><br>Final statistics:<br>Job 0 -- Response: 0.00 Turnaround 28.00 Wait 18.00<br>Job 1 -- Response: 1.00 Turnaround 49.00 Wait 29.00<br>Job 2 -- Response: 2.00 Turnaround 60.00 Wait 30.00<br><br>Average -- Response: 1.00 Turnaround 45.67 Wait 25.67<br></code></pre></td></tr></table></figure> </details><p>采用<code>RR</code>策略的时候通过轮转运行三个程序,达到类似"同时"运行程序的效果,缩短了任务的反应时间。缺点是由于轮询过程会同时运行其他任务,因此总体的完成时刻和等待时间都会延长。</p></li><li><p>由于SJF是“短任务优先”的调度策略,因此当到达的任务顺序为先短时间的任务,后长时间任务的时候,SJF和FIFO的周转时间是相同的</p></li><li><p>当RR策略的量子时间大于等于SJF的单个任务最长工作时间时,SJF和RR可以提供相同的响应时间</p></li><li><p>当工作长度逐渐增加的时候,SJF的响应时间会逐渐增加,因为SJF必须要完成一个完整的任务才会运行下一个任务,因此后面的任务响应时间必须等待前一个任务的完成,模拟省略。</p></li><li><p>假定所有任务的长度都大于量子长度,且完成任务的时间都为量子长度的倍数,则可推得以下公式</p></li></ol><p><span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mfrac><mrow><munderover><mo>∑</mo><mrow><mi>i</mi><mo>=</mo><mn>1</mn></mrow><mrow><mi>N</mi><mo>−</mo><mn>1</mn></mrow></munderover><mi>i</mi><mi>Q</mi></mrow><mi>N</mi></mfrac><mo>=</mo><mfrac><mrow><mi>Q</mi><munderover><mo>∑</mo><mrow><mi>i</mi><mo>=</mo><mn>1</mn></mrow><mrow><mi>N</mi><mo>−</mo><mn>1</mn></mrow></munderover><mi>i</mi></mrow><mi>N</mi></mfrac><mo>=</mo><mfrac><mrow><mi>Q</mi><mo>∗</mo><mfrac><mrow><mi>N</mi><mo stretchy="false">(</mo><mi>N</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><mn>2</mn></mfrac></mrow><mi>N</mi></mfrac><mo>=</mo><mfrac><mrow><mi>Q</mi><mo stretchy="false">(</mo><mi>N</mi><mo>−</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">\frac{\sum_{i=1}^{N-1} iQ}{N}=\frac{Q \sum_{i=1}^{N-1} i}{N}=\frac{Q * \frac{N(N-1)}{2}}{N}=\frac{Q(N-1)}{2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.356941em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.670941em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.10903em;">N</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.6897100000000003em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop"><span class="mop op-symbol small-op" style="position:relative;top:-0.0000050000000000050004em;">∑</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.981231em;"><span style="top:-2.40029em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight">i</span><span class="mrel mtight">=</span><span class="mord mtight">1</span></span></span></span><span style="top:-3.2029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight" style="margin-right:0.10903em;">N</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.29971000000000003em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathdefault">i</span><span class="mord mathdefault">Q</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.356941em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.670941em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.10903em;">N</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.6897100000000003em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">Q</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mop"><span class="mop op-symbol small-op" style="position:relative;top:-0.0000050000000000050004em;">∑</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.981231em;"><span style="top:-2.40029em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight">i</span><span class="mrel mtight">=</span><span class="mord mtight">1</span></span></span></span><span style="top:-3.2029em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight" style="margin-right:0.10903em;">N</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.29971000000000003em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathdefault">i</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.431em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.745em;"><span style="top:-2.324em;"><span class="pstrut" style="height:3.01em;"></span><span class="mord"><span class="mord mathdefault" style="margin-right:0.10903em;">N</span></span></span><span style="top:-3.2399999999999998em;"><span class="pstrut" style="height:3.01em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.745em;"><span class="pstrut" style="height:3.01em;"></span><span class="mord"><span class="mord mathdefault">Q</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.01em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.485em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathdefault mtight" style="margin-right:0.10903em;">N</span><span class="mopen mtight">(</span><span class="mord mathdefault mtight" style="margin-right:0.10903em;">N</span><span class="mbin mtight">−</span><span class="mord mtight">1</span><span class="mclose mtight">)</span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.113em;vertical-align:-0.686em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathdefault">Q</span><span class="mopen">(</span><span class="mord mathdefault" style="margin-right:0.10903em;">N</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span><span class="mclose">)</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>]]></content>
<categories>
<category>知识记录</category>
</categories>
<tags>
<tag>OS</tag>
</tags>
</entry>
<entry>
<title>OSTEP:程序上下文切换的开销</title>
<link href="/p/4b65fa48.html"/>
<url>/p/4b65fa48.html</url>
<content type="html"><![CDATA[<h2 id="第六章受限制直接执行上下文切换"><a class="markdownIt-Anchor" href="#第六章受限制直接执行上下文切换"></a> 第六章:受限制直接执行/上下文切换</h2><h3 id="实验环境"><a class="markdownIt-Anchor" href="#实验环境"></a> 实验环境</h3><p>由于该实验要求在单个CPU上运行两个进程并在他们两个UNIX管道,而书中介绍的<code>sche_affinity()</code>函数的具体调用不是很清楚,所以这里通过<code>Docker</code>的参数限制,创建了一个只使用宿主机一个CPU资源的容器进行实验。</p><p>单核<code>Docker</code>容器的创建</p><figure class="highlight docker"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs docker">docker <span class="hljs-keyword">run</span><span class="language-bash"> -it -d --cpuset-cpus=<span class="hljs-string">"0"</span> --name=os ubuntu:latest</span><br></code></pre></td></tr></table></figure><blockquote><p>注:在以上环境中如果使用函数查询CPU核心数依旧可以发现为<code>16</code>或其他多核,但是在通过指令<code>stress -c 4</code>实际测试后,性能只会在宿主机的单一CPU核心上运行,不影响实验。但是如果在创建<code>Docker</code>容器的时候使用的是<code>--cpus=1</code>,由于负载均衡,并不能达到单核进行实验的目的。</p></blockquote><h3 id="测量思路"><a class="markdownIt-Anchor" href="#测量思路"></a> 测量思路</h3><ol><li><p>通过<code>gettimeofday()</code>增加时间戳函数,获取执行时间</p></li><li><p>创建10个管道,循环5次,每次循环的时候分别在两个管道之间反复通信,并输出上下文切换时间差</p></li></ol><h3 id="代码实现"><a class="markdownIt-Anchor" href="#代码实现"></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><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></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><unistd.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdint.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><sys/time.h></span></span><br><br><span class="hljs-comment">// 标记时间戳</span><br><span class="hljs-type">uint64_t</span> <span class="hljs-title function_">getTimeTick</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">timeval</span> <span class="hljs-title">tv</span>;</span><br> gettimeofday(&tv, <span class="hljs-literal">NULL</span>);<br> <span class="hljs-keyword">return</span> tv.tv_usec;<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">()</span><br>{<br> <span class="hljs-comment">// 创建十个管道用于读写测试</span><br> <span class="hljs-type">int</span> fd[<span class="hljs-number">10</span>][<span class="hljs-number">2</span>];<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10</span>; i++)<br> {<br> <span class="hljs-keyword">if</span> (pipe(fd[i]) < <span class="hljs-number">0</span>)<br> {<br> perror(<span class="hljs-string">"pipe"</span>);<br> <span class="hljs-built_in">exit</span>(<span class="hljs-number">1</span>);<br> }<br> }<br> <span class="hljs-type">char</span> timeWrite[<span class="hljs-number">256</span>], timeRead[<span class="hljs-number">256</span>];<br> <span class="hljs-comment">// 创建子进程</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">9</span>; i += <span class="hljs-number">2</span>)<br> {<br> <span class="hljs-type">int</span> rc = fork();<br> <span class="hljs-keyword">switch</span> (rc)<br> {<br> <span class="hljs-keyword">case</span> <span class="hljs-number">-1</span>: <span class="hljs-comment">// error</span><br> perror(<span class="hljs-string">"fork"</span>);<br> <span class="hljs-built_in">exit</span>(EXIT_FAILURE);<br> <span class="hljs-keyword">case</span> <span class="hljs-number">0</span>:<br> <span class="hljs-comment">// 从管道一中读取数据,如果管道一中没有数据,则阻塞等待</span><br> read(fd[i][<span class="hljs-number">0</span>], timeRead, <span class="hljs-keyword">sizeof</span>(timeRead));<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Read - Write %d: %lu\n"</span>, i, getTimeTick() - atol(timeRead));<br> <span class="hljs-comment">// 将时间写入管道二</span><br> <span class="hljs-built_in">sprintf</span>(timeWrite, <span class="hljs-string">"%lu"</span>, getTimeTick());<br> write(fd[i + <span class="hljs-number">1</span>][<span class="hljs-number">1</span>], timeWrite, <span class="hljs-keyword">sizeof</span>(timeWrite));<br> <span class="hljs-comment">// 从管道一中读取数据,并计算进程切换的总时间</span><br> <span class="hljs-built_in">exit</span>(<span class="hljs-number">0</span>);<br> <span class="hljs-keyword">default</span>:<br> <span class="hljs-comment">// 将时间写入管道一</span><br> <span class="hljs-built_in">sprintf</span>(timeWrite, <span class="hljs-string">"%lu"</span>, getTimeTick());<br> write(fd[i][<span class="hljs-number">1</span>], timeWrite, <span class="hljs-keyword">sizeof</span>(timeWrite));<br> <span class="hljs-comment">// 由于在执行完write之后会继续执行主进程,下方的read也会运行,因此最后结果中奇数进程的结果时间会比偶数进程的时间长,正确答案应该靠近偶数</span><br> <span class="hljs-comment">// 从管道二读取数据,如果管道二中没有数据,则阻塞等待</span><br> read(fd[i + <span class="hljs-number">1</span>][<span class="hljs-number">0</span>], timeRead, <span class="hljs-keyword">sizeof</span>(timeRead));<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Read - Write %d: %lu\n"</span>, i + <span class="hljs-number">1</span>, getTimeTick() - atol(timeRead));<br> <span class="hljs-keyword">break</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="测量结果"><a class="markdownIt-Anchor" href="#测量结果"></a> 测量结果</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs bash">Read - Write 0: 24<br>Read - Write 1: 7<br>Read - Write 2: 16<br>Read - Write 3: 6<br>Read - Write 4: 17<br>Read - Write 5: 7<br>Read - Write 6: 17<br>Read - Write 7: 6<br>Read - Write 8: 18<br>Read - Write 9: 9<br></code></pre></td></tr></table></figure><h3 id="结果差错"><a class="markdownIt-Anchor" href="#结果差错"></a> 结果差错</h3><p>由于在执行完write之后会继续执行主进程,下方的read也会运行,因此最后结果中奇数进程的结果时间会比偶数进程的时间长,正确答案应该靠近偶数(已经在代码中用注释写明)。</p>]]></content>