-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
757 lines (364 loc) · 850 KB
/
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>卸载小米系列机型的预装软件</title>
<link href="/posts/2024/05/04/tutorial/Uninstall-Pre-installed-Apps-on-Xiaomi-Phones/"/>
<url>/posts/2024/05/04/tutorial/Uninstall-Pre-installed-Apps-on-Xiaomi-Phones/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="https://zhuanlan.zhihu.com/p/632050682"> <div class="tag-link-tips">引用站外地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://static.zhihu.com/heifetz/assets/apple-touch-icon-152.81060cab.png);"></div> <div class="tag-link-right"> <div class="tag-link-title">参考链接</div> <div class="tag-link-sitename"> adb删除卸载小米、红米手机(K50)预装系统软件和系统服务</div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="一、手动卸载"><a href="#一、手动卸载" class="headerlink" title="一、手动卸载"></a>一、手动卸载</h2><h3 id="1-1-桌面长按卸载"><a href="#1-1-桌面长按卸载" class="headerlink" title="1.1 桌面长按卸载"></a>1.1 桌面长按卸载</h3><ul><li>如:游戏中心、小米商城、电子邮件、指南针、扫一扫、全球上网、米家、多看读书、喜马拉雅</li><li>可保留:天气、计算器、录音机、笔记</li></ul><h3 id="1-2-应用管理卸载"><a href="#1-2-应用管理卸载" class="headerlink" title="1.2 应用管理卸载"></a>1.2 应用管理卸载</h3><ul><li><p>设置→应用设置→应用管理</p></li><li><p>如:百度输入法小米版、内容中心、小米画报、垃圾清理</p></li></ul><h2 id="二、ADB命令卸载"><a href="#二、ADB命令卸载" class="headerlink" title="二、ADB命令卸载"></a>二、ADB命令卸载</h2><h3 id="2-1-基础命令行"><a href="#2-1-基础命令行" class="headerlink" title="2.1 基础命令行"></a>2.1 基础命令行</h3><ul><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><span class="line">adb shell pm list packages</span><br></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></pre></td><td class="code"><pre><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.android.browser <span class="comment">#卸载小米浏览器</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="2-2-应用列表"><a href="#2-2-应用列表" class="headerlink" title="2.2 应用列表"></a>2.2 应用列表</h3><h4 id="2-2-1-替换为其它软件"><a href="#2-2-1-替换为其它软件" class="headerlink" title="2.2.1 替换为其它软件"></a>2.2.1 替换为其它软件</h4><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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.sohu.inputmethod.sogou.xiaomi <span class="comment">#小米定制搜狗输入法(替换为Gboard等)</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.android.browser <span class="comment">#浏览器(替换为Via、Chrome等)</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.video <span class="comment">#小米视频(替换为NPlayer等)</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.gallery <span class="comment">#相册(替换为Google相册等)</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.player <span class="comment">#音乐(替换为网易云音乐等)</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.mishare.connectivity <span class="comment">#小米互传(替换为其他互传软件)</span></span><br></pre></td></tr></table></figure><h4 id="2-2-2-可卸载(酌情卸载)"><a href="#2-2-2-可卸载(酌情卸载)" class="headerlink" title="2.2.2 可卸载(酌情卸载)"></a>2.2.2 可卸载(酌情卸载)</h4><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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.personalassistant <span class="comment">#智能助理(负一屏)</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.android.quicksearchbox <span class="comment">#搜索</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.yellowpage <span class="comment">#生活黄页</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.hybrid <span class="comment">#快应用服务框架</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.miservice <span class="comment">#服务与反馈</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.contentextension <span class="comment">#传送门</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.aiasst.service <span class="comment">#小爱通话</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.voiceassist <span class="comment">#小爱同学</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.aiasst.vision <span class="comment">#小爱翻译</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.gamecenter.sdk.service <span class="comment">#游戏服务</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.bugreport <span class="comment">#用户反馈</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.analytics <span class="comment">#小米广告分析(重启会自动安装)</span></span><br></pre></td></tr></table></figure><h4 id="2-2-3-其他可卸载(酌情卸载)"><a href="#2-2-3-其他可卸载(酌情卸载)" class="headerlink" title="2.2.3 其他可卸载(酌情卸载)"></a>2.2.3 其他可卸载(酌情卸载)</h4><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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.systemAdSolution <span class="comment">#小米系统广告解决方案</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.translation.kingsoft <span class="comment">#金山翻译</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.translation.youdao <span class="comment">#有道翻译</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.translation.xmcloud <span class="comment">#小米云翻译</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.translationservice <span class="comment">#翻译服务</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.ab <span class="comment">#小米商城系统组件</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.accessibility <span class="comment">#小米闻声</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.migameservice <span class="comment">#游戏高能时刻</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.voicetrigger <span class="comment">#语音唤醒</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.nextpay <span class="comment">#小米支付</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.payment <span class="comment">#米币支付</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.securityadd <span class="comment"># 游戏加速</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.hybrid.accessory <span class="comment"># 智慧生活</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.macro <span class="comment">#自动连招</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.freeform <span class="comment"># 自由窗口</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.carlink <span class="comment">#CarWith</span></span><br></pre></td></tr></table></figure><h4 id="2-2-4-不确定能否卸载(尽量不要卸载)"><a href="#2-2-4-不确定能否卸载(尽量不要卸载)" class="headerlink" title="2.2.4 不确定能否卸载(尽量不要卸载)"></a>2.2.4 不确定能否卸载(尽量不要卸载)</h4><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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.cit <span class="comment"># CIT手机测试</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.contentcatcher <span class="comment"># 应用程序扩展服务</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.maintenancemode <span class="comment"># 维修模式</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.securityadd <span class="comment"># 游戏加速</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.touchassistant <span class="comment"># 悬浮球</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.tsmclient <span class="comment"># 小米智能卡</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.phrase <span class="comment"># 常用语</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.mipay.wallet <span class="comment"># 小米钱包</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.joyose <span class="comment"># 运动计步</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.mirror <span class="comment"># MIUI+ Beta版</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.mircs <span class="comment"># RCS 增强短信</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.xiaomi.otrpbroker <span class="comment"># OTRP 协议协商程序(物联网)</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.micloudsync <span class="comment"># 小米云同步</span></span><br><span class="line">adb shell pm uninstall <span class="literal">--user</span> <span class="number">0</span> com.miui.cloudservice.sysbase <span class="comment"># 小米云服务系统基础</span></span><br><span class="line"><span class="comment"># 其他不确定:小米智能卡、小米智能卡网页组件、小米互联通信服务、米币中心</span></span><br></pre></td></tr></table></figure><h4 id="2-2-5-不可卸载"><a href="#2-2-5-不可卸载" class="headerlink" title="2.2.5 不可卸载"></a>2.2.5 不可卸载</h4><table><thead><tr><th align="center">*应用商店</th><th align="center">应用包管理组件</th><th align="center">小米服务框架</th></tr></thead><tbody><tr><td align="center">手机管家</td><td align="center">*小米云备份</td><td align="center">*小米云服务</td></tr><tr><td align="center">*小米账号</td><td align="center">银联可信服务安全组件小米版本</td><td align="center">短信</td></tr><tr><td align="center">主题壁纸</td><td align="center">*系统界面组件</td><td align="center">日历</td></tr><tr><td align="center">下载管理</td><td align="center">文件管理</td><td align="center">时钟</td></tr><tr><td align="center">钱包</td><td align="center">联系人</td><td align="center"></td></tr></tbody></table><blockquote><p>【*】卸载后将无法正常开机</p></blockquote>]]></content>
<categories>
<category> 教程 </category>
</categories>
<tags>
<tag> 让移动设备变得更好 </tag>
</tags>
</entry>
<entry>
<title>Anaconda安装教程及命令</title>
<link href="/posts/2024/03/07/tutorial/Anaconda/"/>
<url>/posts/2024/03/07/tutorial/Anaconda/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="几种工具的对比"><a href="#几种工具的对比" class="headerlink" title="几种工具的对比"></a>几种工具的对比</h2><ol><li><p><strong>Anaconda</strong>:</p><ul><li>Anaconda 是一个开源的 Python 发行版,包含了很多数据科学和机器学习相关的库和工具,如NumPy、SciPy、Pandas、Jupyter 等</li><li>Anaconda 提供了 conda 包管理器,用于安装、更新和管理 Python 环境和包</li><li>Anaconda 提供了完整的科学计算环境,易于安装和管理</li></ul></li><li><p><strong>conda</strong>:</p><ul><li>conda 是 Anaconda 的包管理器,也可作为独立工具使用。</li><li>可以用于创建、管理和激活不同的 Python 环境,以及安装、更新和删除包</li><li>与其他包管理器相比,conda 具有跨平台性和环境管理功能更强大的优势。</li></ul></li><li><p><strong>pip</strong>:</p><ul><li>pip 是 Python 的包管理器,用于安装和管理 Python 包</li><li>Python 的默认包管理器,通常与虚拟环境一起使用</li><li>pip 提供了大量的第三方包,并且与 Python 官方发行版一起发布</li></ul><table><thead><tr><th align="center">特点</th><th align="center">pip</th><th align="center">conda</th></tr></thead><tbody><tr><td align="center">包管理器</td><td align="center">Python默认的包管理器</td><td align="center">Anaconda发行版提供的包管理器</td></tr><tr><td align="center">包源</td><td align="center">默认从PyPI下载包</td><td align="center">可从Anaconda仓库和其他channels下载包</td></tr><tr><td align="center">环境管理</td><td align="center">不直接提供环境管理功能</td><td align="center">提供创建、管理和激活环境的功能</td></tr><tr><td align="center">支持语言</td><td align="center">Python</td><td align="center">Python、R、C/C++、Java、Lua</td></tr><tr><td align="center">使用场景</td><td align="center">管理Python包</td><td align="center">可用于科学计算和数据处理等领域</td></tr></tbody></table></li><li><p><strong>virtualenv</strong>:</p><ul><li>virtualenv 是一个用于创建独立 Python 环境的工具,可以在同一台计算机上同时运行多个互不干扰的 Python 环境</li><li>virtualenv 可以帮助解决不同项目对于特定依赖包版本的需求,从而避免版本冲突和污染系统环境</li></ul></li></ol><h2 id="安装教程"><a href="#安装教程" class="headerlink" title="安装教程"></a>安装教程</h2><ul><li><p>前往<a href="https://www.anaconda.com/download/">Anaconda官网</a>下载安装包</p></li><li><p>运行安装包</p></li><li><p>点击<code>Next</code>下一步</p></li><li><p>点击<code>I Agree</code>下一步</p></li><li><p>选择<code>Just Me(recommended)</code>,点击<code>Next</code>下一步</p></li><li><p>自行选择是否修改安装目录,如需修改,则注意安装路径<strong>不能包含空格</strong>,点击<code>Next</code>下一步</p></li><li><p>选择可选项</p><ul><li>创建开始菜单快捷方式(Create start menu shortcuts)</li><li>将Anaconda3添加到环境变量(Add Anconda3 to my PATH environment variable):勾选后可能会影响其他应用程序的使用</li><li>注册Anaconda3为默认的Python3.11(Register Anaconda3 as my default Python 3.11):除非你打算使用多个版本的Anaconda或Python,否则建议勾选</li><li>完成安装后清除缓存(Clear the package cache upon completion):建议勾选</li></ul></li><li><p>点击<code>Install</code>安装</p></li><li><p>等待安装完毕</p></li><li><p>验证安装是否成功:在Anaconda Prompt内输入命令,如果安装成功将显示conda版本号,如<code>conda 24.1.2</code></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conda <span class="literal">--version</span></span><br></pre></td></tr></table></figure><blockquote><p>如果你在“选择可选项”步骤勾选了“将Anaconda3添加到环境变量”,则可以打开Powershell或cmd输入命令</p></blockquote></li></ul><h2 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h2><h3 id="conda相关"><a href="#conda相关" class="headerlink" title="conda相关"></a>conda相关</h3><ol><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><span class="line">conda <span class="literal">-h</span></span><br><span class="line">conda <span class="literal">--help</span></span><br></pre></td></tr></table></figure></li><li><p>更新conda</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conda update conda</span><br></pre></td></tr></table></figure></li><li><p>显示有关当前安装的Anaconda版本和环境的信息</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">conda info</span><br></pre></td></tr></table></figure></li></ol><h3 id="环境相关"><a href="#环境相关" class="headerlink" title="环境相关"></a>环境相关</h3><ol><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><span class="line">conda create <span class="literal">--name</span> myenv python=<span class="number">3.8</span> <span class="comment">#创建一个名为“myenv”的新环境,并安装Python 3.8</span></span><br></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></pre></td><td class="code"><pre><span class="line">conda create <span class="literal">--name</span> newenv <span class="literal">--clone</span> oldenv <span class="comment">#创建一个名为“newenv”的新环境,该环境与名为“oldenv”的环境配置相同</span></span><br></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></pre></td><td class="code"><pre><span class="line">conda remove <span class="literal">--name</span> oldenv <span class="literal">--all</span> <span class="comment">#删除一个名为“oldenv”的环境</span></span><br></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></pre></td><td class="code"><pre><span class="line">conda activate myenv <span class="comment">#激活名为“myenv”的环境</span></span><br></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></pre></td><td class="code"><pre><span class="line">conda deactivate</span><br></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><span class="line">conda env list</span><br><span class="line">conda info <span class="literal">-e</span></span><br><span class="line">conda info <span class="literal">--envs</span></span><br></pre></td></tr></table></figure></li></ol><h3 id="包相关"><a href="#包相关" class="headerlink" title="包相关"></a>包相关</h3><ol><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><span class="line">conda list</span><br></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><span class="line">conda install numpy <span class="comment">#在当前环境中安装NumPy包</span></span><br><span class="line">pip install see <span class="comment">#当conda无法安装某个包时,使用pip安装(此处为see包)</span></span><br><span class="line">conda install <span class="literal">--name</span> myenv numpy <span class="comment">#在名为“myenv”的环境中安装NumPy包</span></span><br></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><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">conda update numpy <span class="comment">#更新NumPy包到最新版本</span></span><br><span class="line">conda upgrade numpy <span class="comment">#更新NumPy包到最新版本</span></span><br><span class="line">conda update <span class="literal">--all</span> <span class="comment">#更新所有包</span></span><br><span class="line">conda upgrade <span class="literal">--all</span> <span class="comment">#更新所有包</span></span><br></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><span class="line">conda remove numpy <span class="comment">#从当前环境中删除NumPy包</span></span><br><span class="line">conda remove <span class="literal">--name</span> myenv numpy <span class="comment">#从名为“myenv”的环境中删除NumPy包</span></span><br></pre></td></tr></table></figure></li><li><p>在Anaconda仓库中搜索特定的包</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><span class="line">conda search tensor <span class="comment">#模糊搜索名为tensor的包</span></span><br><span class="line">conda search <span class="literal">--full-name</span> tensorflow <span class="comment">#精确搜索名为tensorflow的包</span></span><br></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></pre></td><td class="code"><pre><span class="line">conda clean <span class="literal">--all</span> <span class="comment">#清理所有不再需要的包和缓存文件</span></span><br></pre></td></tr></table></figure></li></ol>]]></content>
<categories>
<category> 教程 </category>
</categories>
<tags>
<tag> 安装教程 </tag>
<tag> 环境配置 </tag>
</tags>
</entry>
<entry>
<title>博客搭建指南(一)</title>
<link href="/posts/2024/02/01/tutorial/Blog-Building-Guide-1/"/>
<url>/posts/2024/02/01/tutorial/Blog-Building-Guide-1/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>从2022年8月19日购买域名开始搭建小站,到如今正式发布V3.0的博客(以及相关网站矩阵如个人主页、Memos、相册等),我在博客搭建的过程中经历了许多困难,比如内容不尽充实丰富、自定义程度低、博客访问速度慢。这些问题,最终都通过各种方式得到解决。在博客搭建指南系列文章中,我将从博客功能、开发周边网站、博客优化等三个方面向大家介绍我的博客搭建之旅。</p><h2 id="导航栏"><a href="#导航栏" class="headerlink" title="导航栏"></a>导航栏</h2><ul><li>经过几次修改,我的导航栏主要分为以下三部分<ul><li>左侧更多按钮和LOGO,悬浮在更多按钮可查看应用、线路和友链等相关网站,点击LOGO可返回首页</li><li>中间导航菜单,包括文章、空间、站点、关于等菜单</li><li>右侧快捷按钮,包括搜索、开往、随机文章、控制台等功能</li></ul></li><li>在控制台-外观中,提供了固定导航栏的选项,默认不勾选;勾选固定导航栏后,当页面下滑时,导航栏将始终显示,当页面上滑时,导航菜单将被替换为页面名称或文章名称,点击可返回顶部</li><li>当页面下滑时,右侧快捷按钮中将出现提示阅读进度百分比的按钮,点击可返回顶部</li></ul><h2 id="侧边按钮"><a href="#侧边按钮" class="headerlink" title="侧边按钮"></a>侧边按钮</h2><ul><li>相较于Butterfly的默认侧边按钮,我对其进行了精简和修改,包含以下功能<ul><li>在非文章页面,包括切换深浅色模式和跳转到页面底部的按钮</li><li>在文章页面,还包括跳转到评论区和开关评论弹幕的按钮</li><li>在移动端的文章页面,还包括打开目录的按钮</li></ul></li></ul><h2 id="右键菜单"><a href="#右键菜单" class="headerlink" title="右键菜单"></a>右键菜单</h2><ul><li>右键菜单功能分为小按钮栏和功能栏<ul><li>小按钮栏常驻,包含回到首页、刷新、控制台、回到顶部等功能</li><li>功能栏列表如下</li></ul></li></ul><table><thead><tr><th align="center">分组</th><th align="center">内容</th><th align="center">非文章页</th><th align="center">文章页</th><th align="center">备注</th></tr></thead><tbody><tr><td align="center">默认分组</td><td align="center">简繁切换、深浅色模式切换、协议声明</td><td align="center">显示</td><td align="center">显示</td><td align="center"></td></tr><tr><td align="center">通用菜单</td><td align="center">归档、标签、专栏</td><td align="center">显示</td><td align="center">不显示</td><td align="center"></td></tr><tr><td align="center">文章功能</td><td align="center">转到评论、复制本文地址</td><td align="center">不显示</td><td align="center">显示</td><td align="center"></td></tr><tr><td align="center">其他文章功能</td><td align="center">播放/暂停音乐、阅读模式、单双栏切换</td><td align="center">不显示</td><td align="center">显示</td><td align="center"></td></tr><tr><td align="center">文本菜单</td><td align="center">复制文本、引用至评论、Google/Bing/Baidu搜索</td><td align="center"></td><td align="center"></td><td align="center">选中文本时显示</td></tr><tr><td align="center">图片菜单</td><td align="center">复制链接、新标签页打开、全屏显示、保存图片</td><td align="center"></td><td align="center"></td><td align="center">图片上右键时显示</td></tr><tr><td align="center">链接菜单</td><td align="center">复制链接、新标签页打开、转到链接</td><td align="center"></td><td align="center"></td><td align="center">链接上右键时显示</td></tr><tr><td align="center">文本框菜单</td><td align="center">粘贴</td><td align="center"></td><td align="center"></td><td align="center">文本框内右键时显示</td></tr></tbody></table><ul><li>在控制台底部的快捷按钮中,提供了切换默认/自定义右键菜单的选项,默认勾选;勾选自定义右键菜单后,按住Ctrl右键单击将显示默认菜单</li><li>在阅读模式下,右键菜单将被屏蔽,默认/自定义右键菜单均不显示</li></ul><h2 id="控制台"><a href="#控制台" class="headerlink" title="控制台"></a>控制台</h2><ul><li>本博客的控制台功能移植自<a href="https://yisous.xyz/">Ariasakaの小窝</a></li><li>控制台分为底部快捷按钮、外观菜单、APlayer菜单、背景菜单<ul><li>底部快捷按钮包括重置设置、简繁切换、深浅色模式切换、阅读模式、单双栏切换、打开/关闭评论弹幕、切换全屏、切换默认/自定义右键菜单、启用/禁用快捷键</li><li>外观菜单包括固定导航栏、显示侧边按钮、侧栏居左/右、深色模式跟随系统设置、繁星效果、噪点效果、文章页自动主题色</li><li>背景菜单包括切换为默认背景、图片背景、渐变色背景、纯色背景</li><li>APlayer菜单包括打开APlayer、切换歌单、解析歌单</li></ul></li></ul><h2 id="快捷键"><a href="#快捷键" class="headerlink" title="快捷键"></a>快捷键</h2><ul><li>本博客的快捷键功能移植自<a href="https://blog.anheyu.com/">安知鱼</a></li><li>快捷键均通过shift触发,列表如下</li></ul><table><thead><tr><th align="center">快捷键</th><th align="center">功能</th><th align="center">记忆方式</th></tr></thead><tbody><tr><td align="center">?</td><td align="center">查看辅助功能</td><td align="center"></td></tr><tr><td align="center">S</td><td align="center">站内搜索</td><td align="center">search</td></tr><tr><td align="center">R</td><td align="center">随机访问</td><td align="center">random</td></tr><tr><td align="center">H</td><td align="center">返回首页</td><td align="center">home</td></tr><tr><td align="center">C</td><td align="center">打开/关闭控制台</td><td align="center">console</td></tr><tr><td align="center">P</td><td align="center">播放/暂停音乐</td><td align="center">play</td></tr><tr><td align="center">D</td><td align="center">深色/浅色显示模式</td><td align="center">darkmode</td></tr><tr><td align="center">L</td><td align="center">「挚爱」页面</td><td align="center">love</td></tr><tr><td align="center">E</td><td align="center">「即刻」页面</td><td align="center">essay</td></tr><tr><td align="center">G</td><td align="center">「画廊」页面</td><td align="center">gallery</td></tr><tr><td align="center">T</td><td align="center">「藏宝」页面</td><td align="center">treasure</td></tr><tr><td align="center">M</td><td align="center">「天籁」页面</td><td align="center">music</td></tr><tr><td align="center">A</td><td align="center">「关于」页面</td><td align="center">about</td></tr></tbody></table><ul><li>在控制台底部的快捷按钮中,提供了启用/禁用快捷键的选项,默认勾选</li></ul>]]></content>
<categories>
<category> 教程 </category>
</categories>
<tags>
<tag> Hexo </tag>
<tag> 博客搭建 </tag>
</tags>
</entry>
<entry>
<title>Pytest文档学习笔记</title>
<link href="/posts/2023/11/30/documention/Pytest-Learning/"/>
<url>/posts/2023/11/30/documention/Pytest-Learning/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="命令行参数"><a href="#命令行参数" class="headerlink" title="命令行参数"></a>命令行参数</h2><ul><li>-v: 输出详细信息</li><li>-s: 输出调试信息(如 print)</li><li>-x: 遇到失败的测试用例立即停止测试</li><li>-m: 执行特定的标记(mark) <code>pytest -m "mark1 or mark2</code></li><li>-k: 执行包含特定字符串的测试用例 </li><li>–maxfail=num: 当失败测试用例数量达到num时,停止测试</li><li>–ff (–failed-first): 之前运行失败的测试用例会首先被运行,然后才运行其他的测试用例</li><li>–lf (–last-failed): 仅运行上次运行失败的测试用例</li><li>–showlocals(简写-l): 调试失败的测试用例。</li><li>–tb=native:这个参数用于在显示测试失败时使用本机(native)的回溯(traceback)格式。</li><li>–assert=plain:这个参数用于设置断言失败时的输出格式为简单模式。简单模式可能对某些情况下的补丁操作更友好。</li><li>–capture=no:这个参数用于禁用输出捕获。在某些情况下,输出捕获可能受到补丁操作的影响,禁用捕获可以帮助诊断问题。</li></ul><h2 id="fixtures"><a href="#fixtures" class="headerlink" title="fixtures"></a>fixtures</h2><h3 id="What-is-fixtures"><a href="#What-is-fixtures" class="headerlink" title="What is fixtures"></a>What is fixtures</h3><ul><li><p>在测试中,<code>fixtures</code> 为测试提供了定义的、可靠的和一致的上下文。这可能包括环境(例如配置了已知参数的数据库)或内容(例如数据集)。</p></li><li><p>当我们运行一个测试时,pytest 会在函数参数中寻找同名的 fixture,一旦找到了某一个fixture,就会去获取相应的 fixture 的返回值,将这个返回值传递给测试函数</p></li><li><p>使用方法,在函数前加注解(装饰器) <code>@pytest.fixtures</code></p></li><li><p>example</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Fruit</span>:</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name</span>):</span><br><span class="line"> self.name = name</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">__eq__</span>(<span class="params">self, other</span>):</span><br><span class="line"> <span class="keyword">return</span> self.name == other.name</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">my_fruit</span>():</span><br><span class="line"> <span class="keyword">return</span> Fruit(<span class="string">"apple"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fruit_basket</span>(<span class="params">my_fruit</span>):</span><br><span class="line"> <span class="keyword">return</span> [Fruit(<span class="string">"banana"</span>), my_fruit]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_my_fruit_in_basket</span>(<span class="params">my_fruit, fruit_basket</span>):</span><br><span class="line"> <span class="keyword">assert</span> my_fruit <span class="keyword">in</span> fruit_basket</span><br></pre></td></tr></table></figure></li><li><p>测试文件中可以多个函数都有 <code>fixture</code> 注解,一个带有 <code>fixture</code> 注解的函数也可以去调用另外一个 <code>fixture</code> 函数</p></li></ul><h3 id="Improvements-over-xUnit-style-setup-x2F-teardown-functions"><a href="#Improvements-over-xUnit-style-setup-x2F-teardown-functions" class="headerlink" title="Improvements over xUnit-style setup/teardown functions"></a>Improvements over xUnit-style setup/teardown functions</h3><ul><li><p>显式命名与声明的使用:</p><ul><li><p>pytest 的 fixture 具有明确的名称,并且通过从测试函数、模块、类或整个项目中声明其使用来激活。</p></li><li><p>这意味着可以清楚地知道每个 fixture 的作用,并且只在需要时调用它们。</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># Fixture with explicit name 'database'</span></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">database</span>():</span><br><span class="line"> <span class="keyword">return</span> Database()</span><br><span class="line"></span><br><span class="line"><span class="comment"># Using the 'database' fixture in a test function</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_database_operations</span>(<span class="params">database</span>):</span><br><span class="line"> <span class="keyword">assert</span> database.query() == expected_result</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>模块化实现:</p><ul><li><p>每个 fixture 的名称触发一个 fixture 函数,这种模块化的实现允许一个 fixture 函数使用其他 fixtures。</p></li><li><p>这种设计使得你可以构建和组织自己的 fixtures,将它们组合在一起以创建更复杂的测试环境。</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">user</span>():</span><br><span class="line"> <span class="keyword">return</span> User()</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">authenticated_user</span>(<span class="params">user</span>):</span><br><span class="line"> <span class="comment"># Using the 'user' fixture inside the 'authenticated_user' fixture</span></span><br><span class="line"> <span class="keyword">return</span> authenticate_user(user)</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>规模化的管理:</p><ul><li><p>从简单的单元测试到复杂的功能测试,pytest 的 fixture 管理可以进行扩展。</p></li><li><p>可以根据配置和组件选项对 fixtures 和测试进行参数化,或者在不同的作用域(函数、类、模块、整个测试会话)中重复使用 fixtures。</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@pytest.fixture(<span class="params">params=[<span class="literal">True</span>, <span class="literal">False</span>]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">feature_enabled</span>(<span class="params">request</span>):</span><br><span class="line"> <span class="keyword">return</span> request.param</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_feature</span>(<span class="params">feature_enabled</span>):</span><br><span class="line"> <span class="keyword">assert</span> feature_enabled</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>简化拆卸逻辑:</p><ul><li><p>可以轻松、安全地管理拆卸逻辑,而不管使用了多少个 fixtures,而不需要手动处理错误或精心安排清理步骤的顺序。</p></li><li><p>能够更容易地处理测试后的清理工作,无论测试使用了多少 fixtures。</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">setup_and_teardown</span>():</span><br><span class="line"> <span class="comment"># Setup code</span></span><br><span class="line"> <span class="keyword">yield</span></span><br><span class="line"> <span class="comment"># Teardown code</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_with_teardown</span>(<span class="params">setup_and_teardown</span>):</span><br><span class="line"> <span class="comment"># Test code</span></span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>支持 xUnit 风格的设置:</p><ul><li>pytest 仍然支持 xUnit 风格的设置。你可以混合使用两种风格,逐步从经典的方式过渡到新的 fixture 风格,也可以从现有的 unittest.TestCase 风格或基于 nose 的项目中开始。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> unittest</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyTestCase</span>(unittest.TestCase):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">setUp</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="comment"># Classic setup code</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_something</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="comment"># Test code</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">tearDown</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="comment"># Classic teardown code</span></span><br><span class="line"></span><br></pre></td></tr></table></figure></li></ul><h3 id="Fixtures-error"><a href="#Fixtures-error" class="headerlink" title="Fixtures error"></a>Fixtures error</h3><ul><li><p>对于测试用例中的 <code>fixtures</code> 最好以线性排序,以确保能够清晰地了解哪个 fixture 先执行,哪个后执行。</p></li><li><p>然而,如果较早的 fixture 存在问题并引发异常,pytest 会停止执行该测试,并标记为具有错误。</p></li><li><p>当一个测试被标记为有错误时,并不意味着测试失败,而是表示测试甚至无法尝试,因为它所依赖的某个组件出现了问题。</p></li><li><p>所以,在编写测试用例时尽量减少不必要的依赖关系的重要性。通过减少依赖,可以确保不相关的问题不会导致我们无法得知测试用例的实际问题,从而帮助更准确地定位和解决测试中的异常情况。</p></li><li><p>example</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">order</span>():</span><br><span class="line"> <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">append_first</span>(<span class="params">order</span>):</span><br><span class="line"> order.append(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">append_second</span>(<span class="params">order, append_first</span>):</span><br><span class="line"> order.extend([<span class="number">2</span>])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">autouse=<span class="literal">True</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">append_third</span>(<span class="params">order, append_second</span>):</span><br><span class="line"> order += [<span class="number">3</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">order</span>):</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br></pre></td></tr></table></figure></li><li><p>假如,由于某种原因,在执行 <code>order.append(1)</code> 的时候出错了,那么我们就无法知道 <code>order.append([2])</code> 和 <code>order += [3]</code> 是否也出错了,当 <code>append_first</code> 抛出异常之后,pytest将不再为 <code>test_order</code> 执行任何的 <code>fixtures</code>,同样也不会执行 <code>test_order</code>,唯一执行的只有 <code>order</code> 和 <code>append_first</code></p></li></ul><h3 id="Built-in-fixtures"><a href="#Built-in-fixtures" class="headerlink" title="Built-in fixtures"></a>Built-in fixtures</h3><ul><li>pytest 提供了一些内置的 fixtrue 函数:</li></ul><h4 id="tmpdir-和-tempdir-factory"><a href="#tmpdir-和-tempdir-factory" class="headerlink" title="tmpdir 和 tempdir_factory"></a>tmpdir 和 tempdir_factory</h4><ul><li><p>内置的 <code>tmpdir</code> 和 <code>tmpdir_factory</code> 负责在测试开始运行前创建临时文件目录,并在测试结束后删除。其主要特性如下所示:</p><ul><li><p>如果测试代码要对文件进行读写操作,可以使用 <code>tmpdir</code> 或 <code>tmpdir_factory</code> 来创建文件或目录,单个测试使用 <code>tmpdir</code> ,多个测试使用 <code>tmpdir_factory</code></p></li><li><p><code>tmpdir</code> 的作用范围为函数(function)级别, <code>tmpdir_factory</code> 作用范围是会话(session)级别</p></li></ul></li><li><p>example</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_tmpDir</span>(<span class="params">tmpdir</span>):</span><br><span class="line"> tmpfileA = tmpdir.join(<span class="string">"testA.txt"</span>)</span><br><span class="line"> tmpSubDir = tmpdir.mkdir(<span class="string">"subDir"</span>)</span><br><span class="line"> tmpfileB = tmpSubDir.join(<span class="string">"testB.txt"</span>)</span><br><span class="line"> tmpfileA.write(<span class="string">"this is pytest tmp file A"</span>)</span><br><span class="line"> tmpfileB.write(<span class="string">"this is pytest tmp file B"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">assert</span> tmpfileA.read() == <span class="string">"this is pytest tmp file A"</span></span><br><span class="line"> <span class="keyword">assert</span> tmpfileB.read() == <span class="string">"this is pytest tmp file B"</span></span><br></pre></td></tr></table></figure></li><li><p><code>tmpdir</code> 的作用范围是函数级别,所以只能针对测试函数使用 tmpdir 创建文件或目录。如果 fixture 作用范围高于函数级别(类、模块、会话),则需要使用 <code>tmpdir_factory</code> 。tmpdir 与 tmpdir_factory 类似,但提供的方法有一些不同,如下所示:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_tmpDir</span>(<span class="params">tmpdir_factory</span>):</span><br><span class="line"> baseTmpDir = tmpdir_factory.getbasetemp()</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"\nbase temp dir is :<span class="subst">{baseTmpDir}</span>"</span>)</span><br><span class="line"> tmpDir_factory = tmpdir_factory.mktemp(<span class="string">"tempDir"</span>)</span><br><span class="line"> tmpfileA = tmpDir_factory.join(<span class="string">"testA.txt"</span>)</span><br><span class="line"> tmpSubDir = tmpDir_factory.mkdir(<span class="string">"subDir"</span>)</span><br><span class="line"> tmpfileB = tmpSubDir.join(<span class="string">"testB.txt"</span>)</span><br><span class="line"> tmpfileA.write(<span class="string">"this is pytest tmp file A"</span>)</span><br><span class="line"> tmpfileB.write(<span class="string">"this is pytest tmp file B"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">assert</span> tmpfileA.read() == <span class="string">"this is pytest tmp file A"</span></span><br><span class="line"> <span class="keyword">assert</span> tmpfileB.read() == <span class="string">"this is pytest tmp file B"</span></span><br></pre></td></tr></table></figure></li><li><p><code>getbasetemp()</code> 用于返回该会话使用的根目录, <code>pytest-NUM</code> 会随着会话的增加而进行自增,pytest 会记录最近几次(官方文档给的默认值为3次)会话使用的根目录,更早的根目录记录则会被清理掉。可通过配置 <code>tmp_path_retention_count</code> 和 <code>tmp_path_retention_policy</code> 来更改:</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[pytest]</span></span><br><span class="line"><span class="attr">tmp_path_retention_count</span> = <span class="number">3</span></span><br><span class="line"><span class="attr">tmp_path_retention_policy</span> = <span class="string">"all"</span></span><br><span class="line"><span class="comment">; all: retains directories for all tests, regardless of the outcome.</span></span><br><span class="line"><span class="comment">; failed: retains directories only for tests with outcome error or failed.</span></span><br><span class="line"><span class="comment">; none: directories are always removed after each test ends, regardless of the outcome.</span></span><br></pre></td></tr></table></figure></li><li><p>另外也可在命令行指定临时目录,如下所示:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pytest --basetemp=<span class="built_in">dir</span></span><br></pre></td></tr></table></figure></li><li><p>如何在其他作用范围内使用临时目录</p><ul><li><p><code>tmpdir_factory</code> 的作用范围是会话级别的, <code>tmpdir</code> 的作用范围是函数级别的。如果需要模块级别或类级别的作用范围的目录,该如何解决?针对这种情况,可以利用 <code>tmpdir_factory</code> 再创建一个 <code>fixture</code></p></li><li><p>example</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># content of conftest.py</span></span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"module"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">readJson</span>(<span class="params">tmpdir_factory</span>):</span><br><span class="line"> jsonData={</span><br><span class="line"> <span class="string">"name"</span>:<span class="string">"zhangsan"</span>,</span><br><span class="line"> <span class="string">"age"</span>:<span class="number">28</span>,</span><br><span class="line"> <span class="string">"locate"</span>:<span class="string">"shangahi"</span>,</span><br><span class="line"> <span class="string">"loveCity"</span>:{<span class="string">"shanghai"</span>:<span class="string">"shanghai"</span>,</span><br><span class="line"> <span class="string">"wuhai"</span>:<span class="string">"hubei"</span>,</span><br><span class="line"> <span class="string">"shenzheng"</span>:<span class="string">"guangdong"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> file=tmpdir_factory.mktemp(<span class="string">"jsonTemp"</span>).join(<span class="string">"tempJSON.json"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(file,<span class="string">"w"</span>,encoding=<span class="string">"utf8"</span>) <span class="keyword">as</span> fo:</span><br><span class="line"> json.dump(jsonData,fo,ensure_ascii=<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># print(f"base dir is {tmpdir_factory.getbasetemp()}")</span></span><br><span class="line"> <span class="keyword">return</span> file</span><br><span class="line"></span><br><span class="line"><span class="comment"># content of test_module_temdir.py</span></span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_getData</span>(<span class="params">readJson</span>):</span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(readJson,<span class="string">"r"</span>,encoding=<span class="string">"utf8"</span>) <span class="keyword">as</span> fo:</span><br><span class="line"> data=json.load(fo)</span><br><span class="line"> <span class="keyword">assert</span> data.get(<span class="string">"name"</span>)==<span class="string">"zhangsan"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_getLoveCity</span>(<span class="params">readJson</span>):</span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(readJson,<span class="string">"r"</span>,encoding=<span class="string">"utf8"</span>) <span class="keyword">as</span> fo:</span><br><span class="line"> data=json.load(fo)</span><br><span class="line"> getCity=data.get(<span class="string">"loveCity"</span>)</span><br><span class="line"> <span class="keyword">for</span> k,v <span class="keyword">in</span> getCity.items():</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">len</span>(v)><span class="number">0</span></span><br></pre></td></tr></table></figure></li></ul></li></ul><h4 id="tmp-path-和-tmp-path-factory"><a href="#tmp-path-和-tmp-path-factory" class="headerlink" title="tmp_path 和 tmp_path_factory"></a>tmp_path 和 tmp_path_factory</h4><ul><li><p><code>tmp_path</code> 是一个 <code>pathlib.Path</code> 对象.</p></li><li><p>提供了一个临时目录的路径,用于在测试过程中创建和操作临时文件和目录。这个 fixture 在测试中经常用于执行文件操作而不影响实际文件系统。</p></li><li><p>使用方法:</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># content of test_tmp_path.py</span></span><br><span class="line"><span class="keyword">import</span> pathlib</span><br><span class="line">CONTENT = <span class="string">"content"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_create_file</span>(<span class="params">tmp_path: pathlib.WindowsPath</span>):</span><br><span class="line"> d = tmp_path / <span class="string">"sub"</span></span><br><span class="line"> d.mkdir()</span><br><span class="line"> p = d / <span class="string">"hello.txt"</span></span><br><span class="line"> p.write_text(CONTENT, encoding=<span class="string">"utf-8"</span>)</span><br><span class="line"> <span class="keyword">assert</span> p.read_text(encoding=<span class="string">"utf-8"</span>) == CONTENT</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">len</span>(<span class="built_in">list</span>(tmp_path.iterdir())) == <span class="number">1</span></span><br><span class="line"> <span class="keyword">assert</span> <span class="number">0</span></span><br></pre></td></tr></table></figure></li><li><p><code>tmp_path</code> 与 <code>tmpdir</code> 的区别:</p><ul><li><p><code>tmp_path</code> 是一个 <code>pathlib.Path</code> 对象,表示一个临时目录的路径。通常用于执行文件和目录操作。(python standard object)</p></li><li><p><code>tmpdir</code> 是一个 <code>py.path.local</code> 对象,也表示一个临时目录的路径。它提供了一些额外的方法用于文件操作,与 <code>pathlib.Path</code> 有一些不同之处。 (pytest custom designed object)</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> py</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_tmp_path</span>(<span class="params">tmp_path: pathlib.WindowsPath, tmpdir: py.path.LocalPath</span>):</span><br><span class="line"> <span class="comment"># tmp_path 是 pathlib.Path 对象,tmpdir 是 py.path.local 对象</span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"\n<span class="subst">{<span class="built_in">type</span>(tmp_path)}</span>"</span>) <span class="comment"># <class 'pathlib.WindowsPath'></span></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"<span class="subst">{<span class="built_in">type</span>(tmpdir)}</span>"</span>) <span class="comment"># <class '_pytest._py.path.LocalPath'></span></span><br><span class="line"> <span class="comment"># 使用 tmp_path 进行文件操作</span></span><br><span class="line"> tmp_file_path = tmp_path / <span class="string">"example.txt"</span></span><br><span class="line"> tmp_file_path.write_text(<span class="string">"Hello, World!"</span>, encoding=<span class="string">"utf-8"</span>)</span><br><span class="line"> <span class="keyword">assert</span> tmp_file_path.read_text(encoding=<span class="string">"utf-8"</span>) == <span class="string">"Hello, World!"</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 使用 tmpdir 进行文件操作</span></span><br><span class="line"> tmp_file_dir = tmpdir / <span class="string">"example.txt"</span></span><br><span class="line"> tmp_file_dir.write_text(<span class="string">"Hello, Pytest!"</span>, encoding=<span class="string">"utf-8"</span>)</span><br><span class="line"> <span class="keyword">assert</span> tmp_file_dir.read_text(encoding=<span class="string">"utf-8"</span>) == <span class="string">"Hello, Pytest!"</span></span><br></pre></td></tr></table></figure></li><li><p><code>tmp_path_factory</code> 和 <code>tmpdir_factory</code> :</p><ul><li><p><code>tmp_path_factory</code> 是一个工厂函数,用于创建 <code>tmp_path</code> 。每次调用 tmp_path_factory 时,都会返回一个新的 <code>pathlib.Path</code> 对象,表示一个新的临时目录。</p></li><li><p><code>tmpdir_factory</code> 是一个工厂函数,用于创建 <code>tmpdir</code> 。每次调用 tmpdir_factory 时,都会返回一个新的 <code>py.path.local</code> 对象,表示一个新的临时目录。</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pytest <span class="keyword">import</span> TempPathFactory, TempdirFactory</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_tmp_path_factory</span>(<span class="params">tmp_path_factory: TempPathFactory, tmpdir_factory: TempdirFactory</span>):</span><br><span class="line"> <span class="comment"># tmp_path_factory 和 tmpdir_factory 是工厂函数</span></span><br><span class="line"></span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"\n<span class="subst">{<span class="built_in">type</span>(tmp_path_factory)}</span>"</span>) <span class="comment"># <class '_pytest.tmpdir.TempPathFactory'></span></span><br><span class="line"> <span class="built_in">print</span>(<span class="built_in">type</span>(tmpdir_factory)) <span class="comment"># <class '_pytest.legacypath.TempdirFactory'></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 创建新的临时目录,返回 pathlib.Path 对象</span></span><br><span class="line"> tmp_path = tmp_path_factory.mktemp(<span class="string">"test_dir"</span>)</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">isinstance</span>(tmp_path, pathlib.Path)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 创建新的临时目录,返回 py.path.local 对象</span></span><br><span class="line"> tmpdir = tmpdir_factory.mktemp(<span class="string">"test_dir"</span>)</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">isinstance</span>(tmpdir, py.path.local)</span><br></pre></td></tr></table></figure></li></ul><h4 id="pytestconfig"><a href="#pytestconfig" class="headerlink" title="pytestconfig"></a>pytestconfig</h4><ul><li><p>内置的 <code>pytestconfig</code> 可以通过命令行参数、选项、配置文件、插件、运行目录等方式来控制 pytest。pytestconfig 是 <code>request.config</code> 的快捷方式,在 pytest 中称之为 pytest 配置对象</p></li><li><p>该函数可以配合 <code>pytest_addoption</code> 钩子函数一起使用</p></li><li><p>example</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># content of conftest.py</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pytest_addoption</span>(<span class="params">parser</span>):</span><br><span class="line"> parser.addoption(<span class="string">"--myopt"</span>,action=<span class="string">"store_true"</span>,<span class="built_in">help</span>=<span class="string">"test boolean option"</span>)</span><br><span class="line"> parser.addoption(<span class="string">"--foo"</span>,action=<span class="string">"store"</span>,default=<span class="string">"zhangsan"</span>,<span class="built_in">help</span>=<span class="string">"test stroe"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># content of test_pyteestconfig.py</span></span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_myOption</span>(<span class="params">pytestconfig</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"--foo <span class="subst">{pytestconfig.getoption(<span class="string">'foo'</span>)}</span>"</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"--myopt <span class="subst">{pytestconfig.getoption(<span class="string">'myopt'</span>)}</span>"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行:pytest -vs --myopt --foo zhangsan test_pytestconfig.py</span></span><br></pre></td></tr></table></figure></li><li><p>因为 <code>pytestconfig</code> 是一个 fixture 函数,所以也可以被其他 fixture 函数调用</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">foo</span>(<span class="params">pytestconfig</span>):</span><br><span class="line"> <span class="keyword">return</span> pytestconfig.option.foo</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">myopt</span>(<span class="params">pytestconfig</span>):</span><br><span class="line"> <span class="keyword">return</span> pytestconfig.option.myopt</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_fixtureForAddOption</span>(<span class="params">foo,myopt</span>): </span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"\nfoo -- <span class="subst">{foo}</span>"</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"\nmyopt -- <span class="subst">{myopt}</span>"</span>)</span><br></pre></td></tr></table></figure></li><li><p>除了使用 pytestconfig 自定义之外,也可以使用内置的选项和 pytest 启动时的信息,如目录、参数等。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">test_pytestconfig</span>(<span class="params">pytestconfig</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"args : <span class="subst">{pytestconfig.args}</span>"</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"ini file is : <span class="subst">{pytestconfig.inifile}</span>"</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"root dir is : <span class="subst">{pytestconfig.rootdir}</span>"</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"invocation dir is :<span class="subst">{pytestconfig.invocation_dir}</span>"</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"-q, --quiet <span class="subst">{pytestconfig.getoption(<span class="string">'--quiet'</span>)}</span>"</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"-l, --showlocals:<span class="subst">{pytestconfig.getoption(<span class="string">'showlocals'</span>)}</span>"</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"--tb=style: <span class="subst">{pytestconfig.getoption(<span class="string">'tbstyle'</span>)}</span>"</span>)</span><br></pre></td></tr></table></figure></li></ul><h4 id="cache"><a href="#cache" class="headerlink" title="cache"></a>cache</h4><ul><li><p>通常情况下,每个测试用例彼此都是独立的,互不影响。但有时,一个测试用例运行完成后,希望将其结果传递给下一个测试用例,这种情况下,则需要使用pytest内置的cache。</p></li><li><p>为记住上次测试失败的用例,pytest 存储了上一个测试会话中测试失败的信息,可以使用 <code>--cache-show</code> 标识来显示存储的信息。</p></li><li><p>如果需要清空cache,可以在测试会话之前,传入–clear-cache标识即可</p></li><li><p>cache除了–lf和–ff两个标识之外,还可以使用其接口,如下所示:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cache.get(key,default)</span><br><span class="line">cache.<span class="built_in">set</span>(key,value)</span><br></pre></td></tr></table></figure></li><li><p>参考代码</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> datetime</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建一个fixture,记录测试的耗时,并存储到cache中</span></span><br><span class="line"><span class="comment"># 如果后面的测试耗时大于之前的2倍,就抛出超时异常。</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># pytest -vs --cache-clear test_cache.py</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">autouse=<span class="literal">True</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">checkDuration</span>(<span class="params">request,cache</span>):</span><br><span class="line"> key = <span class="string">"duration/"</span>+request.node.nodeid.replace(<span class="string">":"</span>,<span class="string">"_"</span>)</span><br><span class="line"> startTime = datetime.datetime.now()</span><br><span class="line"> <span class="keyword">yield</span></span><br><span class="line"> endTime = datetime.datetime.now()</span><br><span class="line"> duration = (endTime-startTime).total_seconds()</span><br><span class="line"> lastDuration = cache.get(key,<span class="literal">None</span>)</span><br><span class="line"> cache.<span class="built_in">set</span>(key,duration)</span><br><span class="line"> <span class="keyword">if</span> lastDuration <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line"> errorString = <span class="string">"test duration over twice last duration"</span></span><br><span class="line"> <span class="keyword">assert</span> duration <= <span class="number">2</span> * lastDuration,errorString</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.mark.parametrize(<span class="params"><span class="string">"t"</span>,<span class="built_in">range</span>(<span class="params"><span class="number">5</span></span>)</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_duration</span>(<span class="params">t</span>):</span><br><span class="line"> time.sleep(random.randint(<span class="number">0</span>,<span class="number">5</span>))</span><br></pre></td></tr></table></figure></li></ul><h4 id="capsys"><a href="#capsys" class="headerlink" title="capsys"></a>capsys</h4><ul><li><p>pytest内置的capsys主要有两个功能:</p><ul><li><p>允许使用代码读取stdout和stderr</p></li><li><p>可以临时禁止抓取日志输出</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">greeting</span>(<span class="params">name</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"Hello,<span class="subst">{name}</span>"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_greeting</span>(<span class="params">capsys</span>):</span><br><span class="line"> greeting(<span class="string">"Surpass"</span>)</span><br><span class="line"> out,err = capsys.readouterr()</span><br><span class="line"> <span class="keyword">assert</span> <span class="string">"Hello,Surpass"</span> <span class="keyword">in</span> out</span><br><span class="line"> <span class="keyword">assert</span> err == <span class="string">""</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">greeting_err</span>(<span class="params">name</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">f"Hello,<span class="subst">{name}</span>"</span>,file=sys.stderr)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_greeting</span>(<span class="params">capsys</span>):</span><br><span class="line"> greeting_err(<span class="string">"Surpass"</span>)</span><br><span class="line"> out,err = capsys.readouterr()</span><br><span class="line"> <span class="keyword">assert</span> <span class="string">"Hello,Surpass"</span> <span class="keyword">in</span> err</span><br><span class="line"> <span class="keyword">assert</span> out == <span class="string">""</span></span><br></pre></td></tr></table></figure></li><li><p>pytest 通常会抓取测试用例及被测试代码的输出。而且是在全部测试会话结束后,抓取到的输出才会随着失败的测试显示出来。 <code>--s</code> 参数可以关闭该功能,在测试仍在运行时就把输出直接发送到 stdout ,但有时仅需要其中的部分信息,则可以使用 <code>capsys.disable()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">test_capsysDisable</span>(<span class="params">capsys</span>):</span><br><span class="line"> <span class="keyword">with</span> capsys.disabled():</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"\nalways print this information"</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"normal print,usually captured"</span>)</span><br></pre></td></tr></table></figure></li></ul><h4 id="monkeypatch"><a href="#monkeypatch" class="headerlink" title="monkeypatch"></a>monkeypatch</h4><ul><li><p><code>monkey patch</code> 可以<strong>在运行期间对类或模块进行动态修改</strong>。在测试中,monkey patch 常用于替换被测试代码的部分运行环境或装饰输入依赖或输出依赖替换成更容易测试的对象或函数。在 pytest 内置的 monkey patch 允许单一环境中使用,并在测试结束后,无论结果是失败或通过,所有修改都会复原。monkeypatch 常用的函数如下所示:</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></pre></td><td class="code"><pre><span class="line"><span class="built_in">setattr</span>(target, name, value=<notset>, raising=<span class="literal">True</span>): <span class="comment"># 设置属性</span></span><br><span class="line"><span class="built_in">delattr</span>(target, name=<notset>, raising=<span class="literal">True</span>): <span class="comment"># 删除属性</span></span><br><span class="line">setitem(dic, name, value): <span class="comment"># 设置字典中一个元素</span></span><br><span class="line">delitem(dic, name, raising=<span class="literal">True</span>): <span class="comment"># 删除字典中一个元素</span></span><br><span class="line">setenv(name, value, prepend=<span class="literal">None</span>): <span class="comment"># 设置环境变量</span></span><br><span class="line">delenv(name, raising=<span class="literal">True</span>): <span class="comment"># 删除环境变量</span></span><br><span class="line">syspath_prepend(path) <span class="comment"># 将path路径添加到sys.path中</span></span><br><span class="line">chdir(path) <span class="comment"># 改变当前的工作路径</span></span><br></pre></td></tr></table></figure><ul><li><code>raising</code> 参数用于指示 pytest 在记录不存在时,是否抛出异常</li><li><code>setenv()</code> 中的 <code>prepend</code> 可以是一个字符,如果是这样设置,则环境变量的值就是 value+ prepend +</li></ul></li><li><p>不建议对内置函数(如 open、compile 等)进行补丁(patch)操作,因为这可能会破坏 Pytest 的内部实现。如果确实无法避免这样的操作,可以尝试使用一些参数(–tb=native、–assert=plain 和 –capture=no)来减轻可能出现的问题,尽管并不保证能够完全解决。</p></li><li><p>在进行补丁操作时,需要注意对标准库函数和 pytest 使用的一些第三方库进行补丁可能会破坏 pytest 本身。因此,在这些情况下,建议使用 <code>MonkeyPatch.context()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> functools</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_partial</span>(<span class="params">monkeypatch</span>):</span><br><span class="line"> <span class="comment"># 使用 MonkeyPatch.context() 来限制补丁的作用范围</span></span><br><span class="line"> <span class="keyword">with</span> monkeypatch.context() <span class="keyword">as</span> m:</span><br><span class="line"> <span class="comment"># 在这个代码块中对 functools.partial 进行补丁</span></span><br><span class="line"> m.<span class="built_in">setattr</span>(functools, <span class="string">"partial"</span>, <span class="number">3</span>)</span><br><span class="line"> </span><br><span class="line"> <span class="comment"># 在代码块内,functools.partial 被补丁为 3</span></span><br><span class="line"> <span class="keyword">assert</span> functools.partial == <span class="number">3</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 在代码块外,functools.partial 恢复为原来的值</span></span><br><span class="line"> <span class="keyword">assert</span> functools.partial != <span class="number">3</span></span><br></pre></td></tr></table></figure></li><li><p>看如下例子:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"></span><br><span class="line">defaulData={</span><br><span class="line"> <span class="string">"name"</span>:<span class="string">"Surpass"</span>,</span><br><span class="line"> <span class="string">"age"</span>:<span class="number">28</span>,</span><br><span class="line"> <span class="string">"locate"</span>:<span class="string">"shangahi"</span>,</span><br><span class="line"> <span class="string">"loveCity"</span>:{<span class="string">"shanghai"</span>:<span class="string">"shanghai"</span>,</span><br><span class="line"> <span class="string">"wuhai"</span>:<span class="string">"hubei"</span>,</span><br><span class="line"> <span class="string">"shenzheng"</span>:<span class="string">"guangdong"</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">readJSON</span>():</span><br><span class="line"> path=os.path.join(os.getcwd(),<span class="string">"surpass.json"</span>)</span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(path,<span class="string">"r"</span>,encoding=<span class="string">"utf8"</span>) <span class="keyword">as</span> fo:</span><br><span class="line"> data=json.load(fo)</span><br><span class="line"> <span class="keyword">return</span> data</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">writeJSON</span>(<span class="params">data:<span class="built_in">str</span></span>):</span><br><span class="line"> path = os.path.join(os.getcwd(), <span class="string">"surpass.json"</span>)</span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(path,<span class="string">"w"</span>,encoding=<span class="string">"utf8"</span>) <span class="keyword">as</span> fo:</span><br><span class="line"> json.dump(data,fo,ensure_ascii=<span class="literal">False</span>,indent=<span class="number">4</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">writeDefaultJSON</span>():</span><br><span class="line"> writeJSON(defaulData)</span><br></pre></td></tr></table></figure></li><li><p><code>writeDefaultJSON()</code> 既没有参数也没有返回值,该如何测试?仔细观察函数,它会在当前目录中保存一个JSON文件,那就可以从侧面来进行测试。通常比较直接的方法,运行代码并检查文件的生成情况。如下所示:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">test_writeDefaultJSON</span>():</span><br><span class="line"> writeDefaultJSON()</span><br><span class="line"> expectd = defaulData</span><br><span class="line"> actual = readJSON()</span><br><span class="line"> <span class="keyword">assert</span> expectd == actual</span><br></pre></td></tr></table></figure></li><li><p>上面这种方法虽然可以进行测试,但却覆盖了原有文件内容。函数里面所传递的路径为当前目录,那如果将目录换成临时目录了,示例如下所示:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">test_writeDefaultJSONChangeDir</span>(<span class="params">tmpdir, monkeypatch</span>):</span><br><span class="line"> tmpDir = tmpdir.mkdir(<span class="string">"TestDir"</span>)</span><br><span class="line"> monkeypatch.chdir(tmpDir)</span><br><span class="line"> writeDefaultJSON()</span><br><span class="line"> expectd = defaulData</span><br><span class="line"> actual = readJSON()</span><br><span class="line"> <span class="keyword">assert</span> expectd == actual</span><br></pre></td></tr></table></figure></li><li><p>以上这种虽然解决了目录的问题,那如果测试过程,需要修改数据,又该如何,示例如下所示:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">test_writeDefaultJSONChangeDir</span>(<span class="params">tmpdir,monkeypatch</span>):</span><br><span class="line"> tmpDir = tmpdir.mkdir(<span class="string">"TestDir"</span>)</span><br><span class="line"> monkeypatch.chdir(tmpDir)</span><br><span class="line"> <span class="comment"># 保存默认数据</span></span><br><span class="line"> writeDefaultJSON()</span><br><span class="line"> copyData = deepcopy(defaulData)</span><br><span class="line"> <span class="comment"># 增加项</span></span><br><span class="line"> monkeypatch.setitem(defaulData, <span class="string">"hometown"</span>, <span class="string">"hubei"</span>)</span><br><span class="line"> monkeypatch.setitem(defaulData, <span class="string">"company"</span>, [<span class="string">"Surpassme"</span>,<span class="string">"Surmount"</span>])</span><br><span class="line"> addItemData = defaulData</span><br><span class="line"> <span class="comment"># 再次保存数据</span></span><br><span class="line"> writeDefaultJSON()</span><br><span class="line"> <span class="comment"># 获取保存的数据</span></span><br><span class="line"> actual = readJSON()</span><br><span class="line"> <span class="keyword">assert</span> addItemData == actual</span><br><span class="line"> <span class="keyword">assert</span> copyData != actual</span><br></pre></td></tr></table></figure></li><li><p>更多例子参考: </p><ul><li><p>Monkeypatching functions</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># contents of test_module.py with source code and the test</span></span><br><span class="line"><span class="keyword">from</span> pathlib <span class="keyword">import</span> WindowsPath</span><br><span class="line"><span class="keyword">from</span> pytest <span class="keyword">import</span> MonkeyPatch</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">getssh</span>():</span><br><span class="line"> <span class="string">"""Simple function to return expanded homedir ssh path."""</span></span><br><span class="line"> <span class="keyword">return</span> WindowsPath.home() / <span class="string">".ssh"</span> <span class="comment"># C:/User/Admin/.ssh</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_getssh</span>(<span class="params">monkeypatch: MonkeyPatch</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="built_in">type</span>(monkeypatch)) <span class="comment"># <class '_pytest.monkeypatch.MonkeyPatch'></span></span><br><span class="line"> <span class="comment"># mocked return function to replace Path.home</span></span><br><span class="line"> <span class="comment"># always return '/abc'</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">mockreturn</span>():</span><br><span class="line"> <span class="keyword">return</span> WindowsPath(<span class="string">"/abc"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Application of the monkeypatch to replace Path.home</span></span><br><span class="line"> <span class="comment"># with the behavior of mockreturn defined above.</span></span><br><span class="line"> monkeypatch.<span class="built_in">setattr</span>(WindowsPath, <span class="string">"home"</span>, mockreturn)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Calling getssh() will use mockreturn in place of Path.home</span></span><br><span class="line"> <span class="comment"># for this test with the monkeypatch.</span></span><br><span class="line"> x = getssh()</span><br><span class="line"> <span class="keyword">assert</span> x == WindowsPath(<span class="string">"/abc/.ssh"</span>)</span><br><span class="line"> </span><br></pre></td></tr></table></figure></li><li><p>Monkeypatching returned objects: building mock classes</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"><span class="keyword">from</span> pytest <span class="keyword">import</span> MonkeyPatch</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_json</span>(<span class="params">url</span>):</span><br><span class="line"> <span class="string">"""Takes a URL, and returns the JSON."""</span></span><br><span class="line"> r = requests.get(url)</span><br><span class="line"> <span class="keyword">return</span> r.json()</span><br><span class="line"></span><br><span class="line"><span class="comment"># custom class to be the mock return value</span></span><br><span class="line"><span class="comment"># will override the requests.Response returned from requests.get</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MockResponse</span>:</span><br><span class="line"> <span class="comment"># mock json() method always returns a specific testing dictionary</span></span><br><span class="line"><span class="meta"> @staticmethod</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">json</span>():</span><br><span class="line"> <span class="keyword">return</span> {<span class="string">"mock_key"</span>: <span class="string">"mock_response"</span>}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># monkeypatched requests.get moved to a fixture</span></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mock_response</span>(<span class="params">monkeypatch: MonkeyPatch</span>):</span><br><span class="line"> <span class="string">"""Requests.get() mocked to return {'mock_key':'mock_response'}."""</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># Any arguments may be passed and mock_get() will always return our</span></span><br><span class="line"> <span class="comment"># mocked object, which only has the .json() method.</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">mock_get</span>(<span class="params">*args, **kwargs</span>):</span><br><span class="line"> <span class="keyword">return</span> MockResponse()</span><br><span class="line"></span><br><span class="line"> <span class="comment"># apply the monkeypatch for requests.get to mock_get</span></span><br><span class="line"> <span class="comment"># let mock_get() replace requests.get()</span></span><br><span class="line"> monkeypatch.<span class="built_in">setattr</span>(requests, <span class="string">"get"</span>, mock_get)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_get_json</span>(<span class="params">mock_response</span>):</span><br><span class="line"> <span class="comment"># get_json, which contains requests.get, uses the monkeypatch</span></span><br><span class="line"> result = get_json(<span class="string">"https://fakeurl"</span>)</span><br><span class="line"> <span class="keyword">assert</span> result[<span class="string">"mock_key"</span>] == <span class="string">"mock_response"</span></span><br></pre></td></tr></table></figure></li><li><p>Monkeypatching environment variables</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> pytest <span class="keyword">import</span> MonkeyPatch</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_os_user_lower</span>():</span><br><span class="line"> <span class="string">"""Simple retrieval function.</span></span><br><span class="line"><span class="string"> Returns lowercase USER or raises OSError."""</span></span><br><span class="line"> username = os.getenv(<span class="string">"USER"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> username <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line"> <span class="keyword">raise</span> OSError(<span class="string">"USER environment is not set."</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> username.lower()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mock_env_user</span>(<span class="params">monkeypatch</span>):</span><br><span class="line"> monkeypatch.setenv(<span class="string">"USER"</span>, <span class="string">"TestingUser"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mock_env_missing</span>(<span class="params">monkeypatch</span>):</span><br><span class="line"> monkeypatch.delenv(<span class="string">"USER"</span>, raising=<span class="literal">False</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># notice the tests reference the fixtures for mocks</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_upper_to_lower</span>(<span class="params">mock_env_user</span>):</span><br><span class="line"> <span class="keyword">assert</span> get_os_user_lower() == <span class="string">"testinguser"</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_raise_exception</span>(<span class="params">mock_env_missing</span>):</span><br><span class="line"> <span class="keyword">with</span> pytest.raises(OSError):</span><br><span class="line"> _ = get_os_user_lower()</span><br></pre></td></tr></table></figure></li></ul></li></ul><h4 id="recwarn"><a href="#recwarn" class="headerlink" title="recwarn"></a>recwarn</h4><ul><li>内置的 recwarn 可以用来检查待测代码产生的警告信息。在 Python 中,我们可以添加警告信息,很像断言,但不阻止程序运行。假如在一份代码,想要停止支持一个已经过时的函数,则可以在代码中设置警告信息,示例如下所示:</li></ul> <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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> warnings</span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">depricateFunc</span>():</span><br><span class="line"> warnings.warn(<span class="string">"This function is not support after 3.8 version"</span>,DeprecationWarning)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_depricateFunc</span>(<span class="params">recwarn</span>):</span><br><span class="line"> depricateFunc()</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">len</span>(recwarn) == <span class="number">1</span></span><br><span class="line"> warnInfo=recwarn.pop()</span><br><span class="line"> <span class="keyword">assert</span> warnInfo.category == DeprecationWarning</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">str</span>(warnInfo.message) == <span class="string">"This function is not support after 3.8 version"</span></span><br></pre></td></tr></table></figure><ul><li><p>recwarn 的值就是一个警告信息列表,列表中的每个警告信息都有4个属性 <code>category</code> 、 <code>message</code> 、 <code>filename</code> 、 <code>lineno</code> 。警告信息 在测试开始后收集,如果待测的警告信息在最后,则可以在信息收集前使用 <code>recwarn.clear()</code> 清除不需要的内容。</p></li><li><p>除 recwarn,还可以使用 <code>pytest.warns()</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> warnings</span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">depricateFunc</span>():</span><br><span class="line"> warnings.warn(<span class="string">"This function is not support after 3.8 version"</span>,DeprecationWarning)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_depricateFunc</span>():</span><br><span class="line"> <span class="keyword">with</span> pytest.warns(<span class="literal">None</span>) <span class="keyword">as</span> warnInfo:</span><br><span class="line"> depricateFunc()</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">len</span>(warnInfo)==<span class="number">1</span></span><br><span class="line"> w=warnInfo.pop()</span><br><span class="line"> <span class="keyword">assert</span> w.category==DeprecationWarning</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">str</span>(w.message) == <span class="string">"This function is not support after 3.8 version"</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="Sharing-test-data"><a href="#Sharing-test-data" class="headerlink" title="Sharing test data"></a>Sharing test data</h3><ul><li><p>如果要在测试用例中导入文件中的数据,推荐将这些文件数据加载到 <code>fixtrues</code> 中,这种做法利用 pytest 的自动缓存机制</p></li><li><p>另一个做法是将数据文件放在一个 <code>tests</code> 文件夹下,要用到一个插件 <code>pytest-datadir</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install pytest-datadir</span><br></pre></td></tr></table></figure><ul><li>假设目录结构如下:</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">project/</span><br><span class="line">|-- tests/</span><br><span class="line">| |-- test_hello.py</span><br><span class="line">|-- data/</span><br><span class="line">| |-- hello.txt</span><br></pre></td></tr></table></figure><ul><li>hello.txt 文件包含一些测试数据</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 pytest-datadir 插件</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_read_hello_file</span>(<span class="params">datadir</span>):</span><br><span class="line"> <span class="comment"># datadir 是一个 fixture,它提供了测试数据目录的路径</span></span><br><span class="line"> file_path = datadir / <span class="string">'hello.txt'</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># 读取文件内容</span></span><br><span class="line"> <span class="keyword">with</span> <span class="built_in">open</span>(file_path, <span class="string">'r'</span>) <span class="keyword">as</span> file:</span><br><span class="line"> content = file.read()</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 在这个例子中,你可以根据文件内容执行相应的断言</span></span><br><span class="line"> <span class="keyword">assert</span> <span class="string">"Hello, World!"</span> <span class="keyword">in</span> content</span><br></pre></td></tr></table></figure></li></ul><h3 id="Fixture-availability"><a href="#Fixture-availability" class="headerlink" title="Fixture availability"></a>Fixture availability</h3><ul><li><p>从测试的角度来看,一个 <code>fixture</code> 只有在其定义的作用域内才能被测试请求到。如果一个 <code>fixture</code> 在类内定义,那么只有在该类内的测试才能请求到它。但如果一个 <code>fixture</code> 在模块的全局作用域内定义,那么该模块内的任何测试,即使是在类内定义的测试,都可以请求到它。</p></li><li><p>一个 <code>fixture</code> 可以请求任何其他 <code>fixture</code>,无论它们在哪里定义,只要请求它们的测试能够看到所有涉及的 <code>fixture</code>。</p></li><li><p>example</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">order</span>():</span><br><span class="line"> <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">outer</span>(<span class="params">order, inner</span>):</span><br><span class="line"> order.append(<span class="string">"outer"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestOne</span>:</span><br><span class="line"><span class="meta"> @pytest.fixture</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">inner</span>(<span class="params">self, order</span>):</span><br><span class="line"> order.append(<span class="string">"one"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">self, order, outer</span>):</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"one"</span>, <span class="string">"outer"</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestTwo</span>:</span><br><span class="line"><span class="meta"> @pytest.fixture</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">inner</span>(<span class="params">self, order</span>):</span><br><span class="line"> order.append(<span class="string">"two"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">self, order, outer</span>):</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"two"</span>, <span class="string">"outer"</span>]</span><br></pre></td></tr></table></figure></li></ul><h3 id="sharing-fixtures-across-multiple-files"><a href="#sharing-fixtures-across-multiple-files" class="headerlink" title="sharing fixtures across multiple files"></a>sharing fixtures across multiple files</h3><ul><li><p><code>conftest.py</code> 文件可以用作在整个目录中提供 <code>fixtures</code> 的手段。在 conftest.py 中定义的 fixtures 可以被该目录中的任何测试使用,而无需显式导入它们(pytest 会自动发现它们)。</p></li><li><p>可以有多个嵌套的目录或包包含自定义测试,每个目录都可以有自己的 <code>conftest.py</code> 文件,其中定义了特定目录下的 <code>fixtures</code>,这些 fixtures 会添加到父目录中的 conftest.py 文件中定义的 fixtures 中,从而构建出一个层级的 fixtures 结构。</p></li><li><p>假定有如下目录结构:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">project/</span><br><span class="line">|-- tests/</span><br><span class="line">| |-- conftest.py # 这个文件定义了全局的 fixture</span><br><span class="line">| |-- test_module1.py</span><br><span class="line">| |-- subdirectory/</span><br><span class="line">| |-- conftest.py # 这个文件定义了子目录专有的 fixture</span><br><span class="line">| |-- test_module2.py</span><br></pre></td></tr></table></figure></li><li><p>各个目录下的文件内容如下:</p><ul><li><p><code>tests/conftest.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">order</span>():</span><br><span class="line"> <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">top</span>(<span class="params">order, innermost</span>):</span><br><span class="line"> order.append(<span class="string">"top"</span>)</span><br></pre></td></tr></table></figure></li><li><p><code>tests/test_module1.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">innermost</span>(<span class="params">order</span>):</span><br><span class="line"> order.append(<span class="string">"innermost top"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">order, top</span>):</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"innermost top"</span>, <span class="string">"top"</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> pytest.main([<span class="string">'-v'</span>, <span class="string">'-s'</span>])</span><br></pre></td></tr></table></figure></li><li><p><code>tests/subdirectory/conftest.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mid</span>(<span class="params">order</span>):</span><br><span class="line"> order.append(<span class="string">"mid subpackage"</span>)</span><br></pre></td></tr></table></figure></li><li><p><code>tests/subdirectory/test_module2.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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">innermost</span>(<span class="params">order, mid</span>):</span><br><span class="line"> order.append(<span class="string">"innermost subpackage"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">order, top</span>):</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"mid subpackage"</span>, <span class="string">"innermost subpackage"</span>, <span class="string">"top"</span>]</span><br></pre></td></tr></table></figure></li><li><p>以 <code>tests/test_module1.py</code> 为例:执行 <code>test_order()</code> 测试时,首先调用 <code>order</code> (定义在 <code>tests/conftest.py</code> 中),返回值为 <code>[]</code> ,接着调用 <code>top</code> ,top 依次调用 <code>order</code> 和 <code>innermost</code> ,但需要注意的是 <code>innermost</code> 具体是哪一个,因为在子目录的测试文件中也有一个 <code>innermost</code> ,这里的 top 调用的是 <code>tests/test_module1.py</code> 里的 <code>innermost</code> ,返回值为 <code>[innermost top]</code> , 再加上 top 的返回值,最终结果为 <code>["innermost top", "top"]</code>,可以通过测试</p></li><li><p>还有一点需要注意:<code>tests/subdirectory/test_module2.py</code> 可以直接调用 <code>order</code> 和 <code>top</code> 而无需显式导入 <code>tests/conftest.py</code>,当调用 <code>top</code> 时, <code>innermost</code> 则为定义在子目录下的 <code>innermost</code> </p></li><li><p>但反过来,在 <code>tests/test_module1.py</code> 中调用 <code>tests/subdirectory/conftest.py</code> 中的 <code>mid</code> 就不可行了</p></li></ul></li></ul><h3 id="Fixture-instantiation-order"><a href="#Fixture-instantiation-order" class="headerlink" title="Fixture instantiation order"></a>Fixture instantiation order</h3><ul><li><p>当执行测试时,确定 fixture 实例化的顺序主要考虑以下3个因素:</p><ul><li><p>作用域(scope): fixture 的作用域是影响实例化顺序的重要因素。不同作用域的 fixture 会在不同阶段实例化,例如,函数级别(function)、模块级别(module)、类级别(class)、以及整个会话级别(session)。</p></li><li><p>依赖关系(dependencies): fixture 之间的依赖关系也会影响它们的实例化顺序。如果一个 fixture 依赖于另一个 fixture,那么被依赖的 fixture 会在依赖它的 fixture 之前被实例化。</p></li><li><p>自动使用(autouse): 如果一个 fixture 被设置为自动使用,它会在其作用域内的所有测试之前被实例化。</p></li></ul></li></ul><h3 id="Higher-scoped-fixtures-are-executed-first"><a href="#Higher-scoped-fixtures-are-executed-first" class="headerlink" title="Higher-scoped fixtures are executed first"></a>Higher-scoped fixtures are executed first</h3><ul><li><p>有更高级作用域的 <code>fixture</code> 将会被先执行,一般的顺序为 <code>session --> package --> module --> class --> function</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"session"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">order</span>():</span><br><span class="line"> <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">func</span>(<span class="params">order</span>):</span><br><span class="line"> order.append(<span class="string">"function"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"class"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">cls</span>(<span class="params">order</span>):</span><br><span class="line"> order.append(<span class="string">"class"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"module"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mod</span>(<span class="params">order</span>):</span><br><span class="line"> order.append(<span class="string">"module"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"package"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pack</span>(<span class="params">order</span>):</span><br><span class="line"> order.append(<span class="string">"package"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"session"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">sess</span>(<span class="params">order</span>):</span><br><span class="line"> order.append(<span class="string">"session"</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestClass</span>:</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">self, func, cls, mod, pack, sess, order</span>):</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"session"</span>, <span class="string">"package"</span>, <span class="string">"module"</span>, <span class="string">"class"</span>, <span class="string">"function"</span>]</span><br></pre></td></tr></table></figure></li></ul><h3 id="Fixtures-of-the-same-order-execute-based-on-dependencies"><a href="#Fixtures-of-the-same-order-execute-based-on-dependencies" class="headerlink" title="Fixtures of the same order execute based on dependencies"></a>Fixtures of the same order execute based on dependencies</h3><ul><li><p>当一个 <code>fixture</code> 依赖另一个 <code>fixture</code> ,被依赖的 fixture 首先执行</p></li><li><p>假定有如下依赖关系:</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">order</span>():</span><br><span class="line"> <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">a</span>(<span class="params">order</span>):</span><br><span class="line"> order.append(<span class="string">"a"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">b</span>(<span class="params">a, order</span>):</span><br><span class="line"> order.append(<span class="string">"b"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">c</span>(<span class="params">b, order</span>):</span><br><span class="line"> order.append(<span class="string">"c"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">d</span>(<span class="params">c, b, order</span>):</span><br><span class="line"> order.append(<span class="string">"d"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">e</span>(<span class="params">d, b, order</span>):</span><br><span class="line"> order.append(<span class="string">"e"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">f</span>(<span class="params">e, order</span>):</span><br><span class="line"> order.append(<span class="string">"f"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">g</span>(<span class="params">f, c, order</span>):</span><br><span class="line"> order.append(<span class="string">"g"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">g, order</span>):</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>, <span class="string">"e"</span>, <span class="string">"f"</span>, <span class="string">"g"</span>]</span><br></pre></td></tr></table></figure><ul><li><p>具体来说:</p><ul><li>g fixture 依赖于 f 和 c。</li><li>f fixture 依赖于 e。</li><li>e fixture 依赖于 d 和 b。</li><li>d fixture 依赖于 c 和 b。</li><li>c fixture 依赖于 b。</li><li>b fixture 依赖于 a。</li><li>a fixture 依赖于 order。</li></ul></li><li><p>fixture 的执行顺序为:</p><ol><li>order fixture 执行,返回一个空列表。</li><li>a fixture 执行,将字符串 “a” 添加到 order 列表。</li><li>b fixture 执行,将字符串 “b” 添加到 order 列表。</li><li>c fixture 执行,将字符串 “c” 添加到 order 列表。</li><li>d fixture 执行,将字符串 “d” 添加到 order 列表。</li><li>e fixture 执行,将字符串 “e” 添加到 order 列表。</li><li>f fixture 执行,将字符串 “f” 添加到 order 列表。</li><li>g fixture 执行,将字符串 “g” 添加到 order 列表。</li></ol></li><li><p>在上述代码中,每个 fixture 只被执行一次。这是因为默认情况下,pytest 的 fixture 的作用域是函数级别(function scope),每个测试函数调用时,相关的 fixture 会被执行一次。</p></li><li><p>关于多次调用同一个 fixture 的补充说明:</p></li><li><p>这一点可参考官方文档:Fixtures can also be requested more than once during the same test, and <strong>pytest won’t execute them again for that test</strong>. This means we can request fixtures in multiple fixtures that are dependent on them (and even again in the test itself) without those fixtures being executed more than once. </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></pre></td><td class="code"><pre><span class="line"><span class="comment"># contents of test_append.py</span></span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># Arrange</span></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">first_entry</span>():</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"a"</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># Arrange</span></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">order</span>():</span><br><span class="line"> <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># Act</span></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">append_first</span>(<span class="params">order, first_entry</span>):</span><br><span class="line"> <span class="keyword">return</span> order.append(first_entry)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_string_only</span>(<span class="params">append_first, order, first_entry</span>):</span><br><span class="line"> <span class="comment"># Assert</span></span><br><span class="line"> <span class="comment"># If a requested fixture was executed once for every time it was requested during a test</span></span><br><span class="line"> <span class="comment"># then this test would fail because both append_first and test_string_only would see order as an empty list (i.e. [])</span></span><br><span class="line"> <span class="comment"># but since the return value of order was cached (along with any side effects executing it may have had) after the first time it was called,</span></span><br><span class="line"> <span class="comment"># both the test and append_first were referencing the same object</span></span><br><span class="line"> <span class="comment"># and the test saw the effect append_first had on that object.</span></span><br><span class="line"> <span class="keyword">assert</span> order == [first_entry]</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> pytest.main([<span class="string">'-v'</span>, <span class="string">'-s'</span>])</span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="Autouse-fixtures-are-executed-first-within-their-scope"><a href="#Autouse-fixtures-are-executed-first-within-their-scope" class="headerlink" title="Autouse fixtures are executed first within their scope"></a>Autouse fixtures are executed first within their scope</h3><ul><li><p>如果 fixture A 是自动使用的,而 fixture B 不是,但 fixture A 请求了 fixture B,那么在实际应用到 fixture A 的测试中,fixture B 也会被有效地当作自动使用的 fixture。这意味着 fixture B 在这些测试中会在其他非自动使用的 fixtures 之前执行。</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 可以自己运行看看实际的 fixture 函数的 实例化顺序</span></span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">order</span>():</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"order"</span>)</span><br><span class="line"> <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">a</span>(<span class="params">order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"a"</span>)</span><br><span class="line"> order.append(<span class="string">"a"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">b</span>(<span class="params">a, order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"b"</span>)</span><br><span class="line"> order.append(<span class="string">"b"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">autouse=<span class="literal">True</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">c</span>(<span class="params">b, order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"c"</span>)</span><br><span class="line"> order.append(<span class="string">"c"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">d</span>(<span class="params">b, order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"d"</span>)</span><br><span class="line"> order.append(<span class="string">"d"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">e</span>(<span class="params">d, order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"e"</span>)</span><br><span class="line"> order.append(<span class="string">"e"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">f</span>(<span class="params">e, order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"f"</span>)</span><br><span class="line"> order.append(<span class="string">"f"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">g</span>(<span class="params">f, c, order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"g"</span>)</span><br><span class="line"> order.append(<span class="string">"g"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_order_and_g</span>(<span class="params">g, order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"test_order_and_g"</span>)</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"a"</span>, <span class="string">"b"</span>, <span class="string">"c"</span>, <span class="string">"d"</span>, <span class="string">"e"</span>, <span class="string">"f"</span>, <span class="string">"g"</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> pytest.main([<span class="string">'-v'</span>, <span class="string">'-s'</span>])</span><br></pre></td></tr></table></figure></li><li><p>在同一个作用域下,每一个 autouse 的 fixture 会在每个测试前自动执行,即使你并没有显式地请求这个 fixture。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"class"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">order</span>():</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"order"</span>)</span><br><span class="line"> <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"class"</span>, autouse=<span class="literal">True</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">c1</span>(<span class="params">order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"c1 autouse"</span>)</span><br><span class="line"> order.append(<span class="string">"c1"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"class"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">c2</span>(<span class="params">order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"c2"</span>)</span><br><span class="line"> order.append(<span class="string">"c2"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture(<span class="params">scope=<span class="string">"class"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">c3</span>(<span class="params">order, c1</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"c3"</span>)</span><br><span class="line"> order.append(<span class="string">"c3"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestClassWithC1Request</span>:</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">self, order, c1, c3</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"test_order c1"</span>)</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"c1"</span>, <span class="string">"c3"</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestClassWithoutC1Request</span>:</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_order</span>(<span class="params">self, order, c2</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"test_order c2"</span>)</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"c1"</span>, <span class="string">"c2"</span>]</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> pytest.main([<span class="string">'-v'</span>, <span class="string">'-s'</span>])</span><br></pre></td></tr></table></figure></li><li><p>即使一个自动使用的 fixture 请求了一个非自动使用的 fixture,这个非自动使用的 fixture 只会在请求它的那个自动使用 fixture 的上下文中被有效地当作自动使用 fixture。这并不会使得非自动使用的 fixture 在所有可能的情境中都变成自动使用的。</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><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">order</span>():</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"order"</span>)</span><br><span class="line"> <span class="keyword">return</span> []</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">c1</span>(<span class="params">order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"c1"</span>)</span><br><span class="line"> order.append(<span class="string">"c1"</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">c2</span>(<span class="params">order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"c2"</span>)</span><br><span class="line"> order.append(<span class="string">"c2"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestClassWithAutouse</span>:</span><br><span class="line"><span class="meta"> @pytest.fixture(<span class="params">autouse=<span class="literal">True</span></span>)</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">c3</span>(<span class="params">self, order, c2</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"c3"</span>)</span><br><span class="line"> order.append(<span class="string">"c3"</span>)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_req</span>(<span class="params">self, order, c1</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"test_req"</span>)</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"c2"</span>, <span class="string">"c3"</span>, <span class="string">"c1"</span>]</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_no_req</span>(<span class="params">self, order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"test_no_req"</span>)</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"c2"</span>, <span class="string">"c3"</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">TestClassWithoutAutouse</span>:</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_req</span>(<span class="params">self, order, c1</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"test_req"</span>)</span><br><span class="line"> <span class="keyword">assert</span> order == [<span class="string">"c1"</span>]</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">test_no_req</span>(<span class="params">self, order</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">"test_no_req"</span>)</span><br><span class="line"> <span class="keyword">assert</span> order == []</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line"> pytest.main([<span class="string">'-v'</span>, <span class="string">'-s'</span>])</span><br></pre></td></tr></table></figure></li></ul><h3 id="yield-fixtures-recommended"><a href="#yield-fixtures-recommended" class="headerlink" title="yield fixtures (recommended)"></a>yield fixtures (recommended)</h3><ul><li><p>yield 替代 return</p><ul><li><p>在使用 yield 定义 fixture 时,它不再使用 return 返回值,而是使用 yield</p></li><li><p>yield 的作用是在测试执行之前运行一些代码,并将一个对象传递回请求该 fixture 或测试的地方。</p></li></ul></li><li><p>拆分 yield 前后的代码</p><ul><li><p>yield 语句之前的代码在测试执行之前运行,用于设置或准备测试环境</p></li><li><p>yield 语句之后的代码在测试执行完成后运行,用于清理或执行拆卸操作</p></li></ul></li><li><p>Fixture 执行顺序</p><ul><li><p>Pytest 会确定 fixtures 的线性顺序,然后依次执行每个 fixture 直到它返回或使用 yield</p></li><li><p>一旦测试完成,Pytest 将按照相反的顺序返回到 fixtures 列表,对每个使用了 yield 的 fixture 运行在 yield 语句之后的代码</p></li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># content of test_emaillib.py</span></span><br><span class="line"><span class="keyword">from</span> emaillib <span class="keyword">import</span> Email, MailAdminClient</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mail_admin</span>():</span><br><span class="line"> <span class="keyword">return</span> MailAdminClient()</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">sending_user</span>(<span class="params">mail_admin</span>):</span><br><span class="line"> user = mail_admin.create_user()</span><br><span class="line"> <span class="keyword">yield</span> user</span><br><span class="line"> mail_admin.delete_user(user)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">receiving_user</span>(<span class="params">mail_admin</span>):</span><br><span class="line"> user = mail_admin.create_user()</span><br><span class="line"> <span class="keyword">yield</span> user</span><br><span class="line"> user.clear_mailbox()</span><br><span class="line"> mail_admin.delete_user(user)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_email_received</span>(<span class="params">sending_user, receiving_user</span>):</span><br><span class="line"> email = Email(subject=<span class="string">"Hey!"</span>, body=<span class="string">"How's it going?"</span>)</span><br><span class="line"> sending_user.send_email(email, receiving_user)</span><br><span class="line"> <span class="keyword">assert</span> email <span class="keyword">in</span> receiving_user.inbox</span><br></pre></td></tr></table></figure></li><li><p>除了使用 yield 之外,还可以使用 “finalizer” 函数</p><ul><li><p>相对于使用 yield 的 fixtures,另一种选择是直接将 “finalizer” 函数添加到测试的请求上下文对象中</p></li><li><p>这种方法达到的结果与使用 yield 的 fixtures 类似,但相对来说需要更多的冗余代码</p></li><li><p>为了使用这种方法,我们需要在需要添加清理代码的 fixture 中请求测试的请求上下文对象,就像请求其他 fixtures 一样。</p></li><li><p>在获取到 request 上下文对象后,可以通过调用其 addfinalizer 方法,将一个包含清理代码的可调用对象传递给它。</p></li><li><p>这个清理代码会在测试执行完成后运行,类似于 yield 的后续代码。</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># content of test_emaillib.py</span></span><br><span class="line"><span class="keyword">from</span> emaillib <span class="keyword">import</span> Email, MailAdminClient</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">mail_admin</span>():</span><br><span class="line"> <span class="keyword">return</span> MailAdminClient()</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">sending_user</span>(<span class="params">mail_admin</span>):</span><br><span class="line"> user = mail_admin.create_user()</span><br><span class="line"> <span class="keyword">yield</span> user</span><br><span class="line"> mail_admin.delete_user(user)</span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">receiving_user</span>(<span class="params">mail_admin, request</span>):</span><br><span class="line"> user = mail_admin.create_user()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">delete_user</span>():</span><br><span class="line"> mail_admin.delete_user(user)</span><br><span class="line"></span><br><span class="line"> request.addfinalizer(delete_user)</span><br><span class="line"> <span class="keyword">return</span> user</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@pytest.fixture</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">email</span>(<span class="params">sending_user, receiving_user, request</span>):</span><br><span class="line"> _email = Email(subject=<span class="string">"Hey!"</span>, body=<span class="string">"How's it going?"</span>)</span><br><span class="line"> sending_user.send_email(_email, receiving_user)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">empty_mailbox</span>():</span><br><span class="line"> receiving_user.clear_mailbox()</span><br><span class="line"></span><br><span class="line"> request.addfinalizer(empty_mailbox)</span><br><span class="line"> <span class="keyword">return</span> _email</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_email_received</span>(<span class="params">receiving_user, email</span>):</span><br><span class="line"> <span class="keyword">assert</span> email <span class="keyword">in</span> receiving_user.inbox</span><br></pre></td></tr></table></figure></li></ul></li></ul><h2 id="Mark"><a href="#Mark" class="headerlink" title="Mark"></a>Mark</h2><ul><li><p>使用 pytest.mark 辅助工具设置测试函数的元数据(metadata)</p></li><li><p>pytest.mark 辅助工具:</p><ul><li>使用 pytest.mark 辅助工具,可以在测试函数上设置元数据。元数据可以是标记、条件等,用于影响测试运行的行为。</li></ul></li><li><p>内置标记:</p><ul><li><p>usefixtures:在测试函数或类上使用 fixtures。</p></li><li><p>filterwarnings:过滤特定的警告。</p></li><li><p>skip:始终跳过测试函数。</p></li><li><p>skipif:如果满足特定条件,则跳过测试函数。</p></li><li><p>xfail:如果满足特定条件,则产生“预期失败”的结果。</p></li><li><p>parametrize:对同一个测试函数使用不同的参数执行多次调用。</p></li></ul></li><li><p>获取所有标记的方法:</p><ul><li>可以使用 pytest –markers 命令行选项列出所有标记,包括内置标记和自定义标记。</li></ul></li><li><p>自定义标记和应用标记的范围:</p><ul><li>可以创建自定义标记,并将标记应用于整个测试类或模块。</li><li>这些标记可以被插件使用,也常用于使用 -m 选项在命令行上选择特定的测试。</li></ul></li><li><p><strong>Note</strong>: mark 只能被用于测试,对 fixture 并没有影响</p></li></ul><h3 id="Registering-marks"><a href="#Registering-marks" class="headerlink" title="Registering marks"></a>Registering marks</h3><ul><li><p>可以在 <code>pytest.ini</code> 文件中设置 markers</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[pytest]</span></span><br><span class="line"><span class="attr">markers</span> =</span><br><span class="line"> slow: marks tests as slow (deselect with '-m "not slow"')</span><br><span class="line"> serial</span><br></pre></td></tr></table></figure></li><li><p>或者在 <code> pyproject.toml</code> 文件中设置 marker</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[tool.pytest.ini_options]</span></span><br><span class="line"><span class="attr">markers</span> = [</span><br><span class="line"> <span class="string">"slow: marks tests as slow (deselect with '-m \"not slow\"')"</span>,</span><br><span class="line"> <span class="string">"serial"</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure></li></ul><h3 id="Raising-errors-on-unknown-marks"><a href="#Raising-errors-on-unknown-marks" class="headerlink" title="Raising errors on unknown marks"></a>Raising errors on unknown marks</h3><ul><li><p>当使用 <code>@pytest.mark.name_of_the_mark</code> 去应用一个 <code>pytest.ini</code> 中未定义的 mark,pytest将会发出警告</p></li><li><p>如果还在 <code>pytest.ini</code> 中声明 <code>--strict-marker</code> ,那么使用未定义的 mark 将会报告错误,而不是警告</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">[pytest]</span></span><br><span class="line"><span class="attr">addopts</span> = --strict-markers</span><br><span class="line"><span class="attr">markers</span> =</span><br><span class="line"> slow: marks tests as slow (deselect with '-m "not slow"')</span><br><span class="line"> serial</span><br></pre></td></tr></table></figure></li></ul><h3 id="parametrize"><a href="#parametrize" class="headerlink" title="parametrize"></a>parametrize</h3><ul><li><p>在 Pytest 中,当将参数值传递给测试函数时,它们是原样传递的,没有进行任何复制。这意味着如果将一个列表或字典作为参数值传递给测试函数,并且测试函数中对该列表或字典进行了修改,这些修改会在后续的测试用例调用中反映出来。</p></li><li><p>example</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># content of test_example.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> copy</span><br><span class="line"><span class="keyword">import</span> pytest</span><br><span class="line"></span><br><span class="line"><span class="comment"># 参数化测试函数,接受一个列表作为参数</span></span><br><span class="line"><span class="meta">@pytest.mark.parametrize(<span class="params"><span class="string">"my_list"</span>, [[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_modify_list</span>(<span class="params">my_list</span>):</span><br><span class="line"> <span class="comment"># 在测试函数中对传递进来的列表进行修改</span></span><br><span class="line"> my_list.append(<span class="number">4</span>)</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">len</span>(my_list) == <span class="number">4</span></span><br><span class="line"></span><br><span class="line"><span class="comment">###########################################################</span></span><br><span class="line"><span class="comment"># 更推荐</span></span><br><span class="line"><span class="comment">###########################################################</span></span><br><span class="line"><span class="meta">@pytest.mark.parametrize(<span class="params"><span class="string">"my_list"</span>, [[<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_modify_list_deep_copy</span>(<span class="params">my_list</span>):</span><br><span class="line"> <span class="comment"># 在测试函数中对传递进来的列表进行深复制</span></span><br><span class="line"> my_list_copy = copy.deepcopy(my_list)</span><br><span class="line"> my_list_copy.append(<span class="number">4</span>)</span><br><span class="line"> <span class="keyword">assert</span> <span class="built_in">len</span>(my_list_copy) == <span class="number">4</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 参数化测试函数,接受一个字典作为参数</span></span><br><span class="line"><span class="meta">@pytest.mark.parametrize(<span class="params"><span class="string">"my_dict"</span>, [{<span class="string">"key"</span>: <span class="string">"value"</span>}]</span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_modify_dict</span>(<span class="params">my_dict</span>):</span><br><span class="line"> <span class="comment"># 在测试函数中对传递进来的字典进行修改</span></span><br><span class="line"> my_dict[<span class="string">"new_key"</span>] = <span class="string">"new_value"</span></span><br><span class="line"> <span class="keyword">assert</span> <span class="string">"new_key"</span> <span class="keyword">in</span> my_dict</span><br></pre></td></tr></table></figure></li></ul><h3 id="pytest-generate-tests"><a href="#pytest-generate-tests" class="headerlink" title="pytest-generate_tests"></a>pytest-generate_tests</h3><ul><li><p>这是 pytest 内置的钩子函数,该钩子在收集测试函数时被调用。通过传递的 <code>metafunc</code> 对象,可以检查请求测试上下文,最重要的是,可以调用 <code>metafunc.parametrize()</code> 来实现参数化。</p></li><li><p>metafunc 是 Pytest 中的一个内置对象,它是 Metafunc 类的实例。Metafunc 类提供了一些方法,允许在测试收集阶段动态生成和配置测试函数。下面是一些 metafunc 常用的方法:</p><ul><li><p><code>fixturenames:</code> 返回测试函数中声明的所有 fixture 的名称的列表</p></li><li><p><code>function:</code> 返回当前测试函数的 Function 对象</p></li><li><p><code>config:</code> 返回 Pytest 配置的 Config 对象</p></li><li><p><code>parametrize:</code> 允许在 pytest_generate_tests 钩子中调用,用于动态生成参数化的测试</p></li></ul></li></ul><h3 id="pytest-addoption"><a href="#pytest-addoption" class="headerlink" title="pytest_addoption"></a>pytest_addoption</h3><ul><li><p><code>pytest_addoption</code> 是 Pytest 中的一个内置函数。它是一个钩子函数,用于在 Pytest 运行时处理命令行选项。用户可以在测试项目中的 <code>conftest.py</code> 文件中定义这个函数,用于添加自定义的命令行选项。</p></li><li><p>example:</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></pre></td><td class="code"><pre><span class="line"><span class="comment"># content of conftest.py</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pytest_addoption</span>(<span class="params">parser</span>):</span><br><span class="line"> parser.addoption(</span><br><span class="line"> <span class="string">"--stringinput"</span>,</span><br><span class="line"> action=<span class="string">"append"</span>,</span><br><span class="line"> default=[],</span><br><span class="line"> <span class="built_in">help</span>=<span class="string">"list of stringinputs to pass to test functions"</span>,</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pytest_generate_tests</span>(<span class="params">metafunc</span>):</span><br><span class="line"> <span class="keyword">if</span> <span class="string">"stringinput"</span> <span class="keyword">in</span> metafunc.fixturenames:</span><br><span class="line"> metafunc.parametrize(<span class="string">"stringinput"</span>, metafunc.config.getoption(<span class="string">"stringinput"</span>))</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># content of test_strings.py</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test_valid_string</span>(<span class="params">stringinput</span>):</span><br><span class="line"> <span class="keyword">assert</span> stringinput.isalpha()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 执行:pytest --stringinput="hello" --stringinput="world" test_hook.py</span></span><br></pre></td></tr></table></figure><ul><li><p>这里对 <code>addoption</code> 函数的 <code>action</code> 参数进行补充说明:</p><ul><li><p><code>store</code> :将选项的值存储在一个单独的变量中。如果同一个选项多次出现,后面的值会覆盖前面的值。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">parser.addoption(<span class="string">"--myoption"</span>, action=<span class="string">"store"</span>, default=<span class="string">"default_value"</span>, <span class="built_in">help</span>=<span class="string">"My custom option"</span>)</span><br></pre></td></tr></table></figure></li><li><p><code>store_const</code> :将选项的值存储为一个常量。通常与 const 参数一起使用。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">parser.addoption(<span class="string">"--myoption"</span>, action=<span class="string">"store_const"</span>, const=<span class="string">"constant_value"</span>, <span class="built_in">help</span>=<span class="string">"My custom option"</span>)</span><br></pre></td></tr></table></figure></li><li><p><code>store_true</code> 和 <code>store_false</code> :用于处理布尔选项,分别表示 True 和 False。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">parser.addoption(<span class="string">"--enable-feature"</span>, action=<span class="string">"store_true"</span>, <span class="built_in">help</span>=<span class="string">"Enable a feature"</span>)</span><br><span class="line">parser.addoption(<span class="string">"--disable-feature"</span>, action=<span class="string">"store_false"</span>, <span class="built_in">help</span>=<span class="string">"Disable a feature"</span>)</span><br></pre></td></tr></table></figure></li><li><p><code>append</code> :如果同一个选项在命令行中出现多次,将其值添加到一个列表中。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">parser.addoption(<span class="string">"--stringinput"</span>, action=<span class="string">"append"</span>, default=[], <span class="built_in">help</span>=<span class="string">"List of stringinputs"</span>)</span><br></pre></td></tr></table></figure></li><li><p><code>count</code> :记录选项在命令行中出现的次数,用于计数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">parser.addoption(<span class="string">"--verbose"</span>, action=<span class="string">"count"</span>, default=<span class="number">0</span>, <span class="built_in">help</span>=<span class="string">"Increase verbosity level"</span>)</span><br></pre></td></tr></table></figure></li><li><p><code>callback</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">callback_function</span>(<span class="params">option, opt_str, value, parser</span>):</span><br><span class="line"> <span class="comment"># Custom logic to handle the option's value</span></span><br><span class="line"> <span class="keyword">pass</span></span><br><span class="line"> parser.addoption(<span class="string">"--custom-option"</span>, action=<span class="string">"callback"</span>, callback=callback_function, <span class="built_in">help</span>=<span class="string">"Custom option"</span>)</span><br></pre></td></tr></table></figure></li></ul></li></ul></li></ul><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="https://docs.pytest.org/en/7.4.x/how-to/index.html"> <div class="tag-link-tips">引用站外地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://docs.pytest.org/en/7.4.x/_static/favicon.png);"></div> <div class="tag-link-right"> <div class="tag-link-title">Pytest文档</div> <div class="tag-link-sitename"> Pytest官方文档</div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="https://www.cnblogs.com/surpassme/p/13258526.html"> <div class="tag-link-tips">引用站外地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://docs.pytest.org/en/7.4.x/_static/favicon.png);"></div> <div class="tag-link-right"> <div class="tag-link-title">pytest built-in fixtures</div> <div class="tag-link-sitename"> pytest built-in fixtures</div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div>]]></content>
<categories>
<category> 文档 </category>
</categories>
<tags>
<tag> API文档 </tag>
</tags>
</entry>
<entry>
<title>Colorful Season</title>
<link href="/posts/2023/11/20/essay/Colorful-Season/"/>
<url>/posts/2023/11/20/essay/Colorful-Season/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p> 无边无尽的黑夜,与远方湖畔的船灯。</p><p> 在凌波门旁的石桥上,早已候着许多前来看日出的人。</p><p> 尽管正值夏季,但日出前后的时辰里,还是有些微凉的。天边有晨星在眨眼,有残月在生辉,地上有恋人在相视,有情侣在拥吻。</p><p> 最靠近东方的石桥上,有两只小鸟依偎着。它们一只黄喙,一只红喙,互相梳着羽毛,望着已经泛白的天际,亲昵不语。</p><p> 湖中不时有鱼儿跃出水面,显得日出前的东湖愈发静谧。已经有人前来晨泳,哗啦啦地游向远方。</p><p> 两只小鸟时而扇动翅膀,时而叽叽喳喳,似在低语。</p><p> 东方与湖面齐平的地方愈发亮了,光亮正赶着黑夜向远方走去。太阳的一角露了出来,在晨风的浮动与游鱼的拨弄下,东湖波光粼粼,煞是好看。</p><p> 紧接着,太阳已经现出了全貌,无尽的阳光开始照耀在一草一木之上。</p><p> 波动的湖水是青色的,蓬勃的朝阳是火红的,葳蕤的树木是葱绿的,而恋人的爱是多彩的。</p><p> 黄喙小鸟用歌喉吟出一首短诗:</p><p> 「日出」</p><p> 阳光驱赶着黑夜</p><p> 世界见证着恋人</p><p> 那游鱼激起的涟漪</p><p> 便是我对你的爱慕</p><p> </p><p> 无边无尽的秋风,与远方树梢的斜阳。</p><p> 在树林环绕的操场上,早已坐着许多前来晒太阳的人。</p><p> 秋日的午后,天高气爽,阳光的温度甚是沁人心脾。空中有落叶在摇曳,有树枝在婆娑,地上有恋人在相视,有情侣在厮磨。</p><p> 两只小鸟缓缓落在操场中心,在这浩瀚的高穹下,不顾旁人,互相梳着羽毛。</p><p> 不时有欢笑声传来,使得这秋风洋溢着快乐。有人在风中散步,哼着好听的歌。</p><p> 两只小鸟时而乘着秋风追逐嬉戏,时而落在草坪上叽喳低语。</p><p> 落日掩映在树枝后,投下斑驳的光影。余晖洒在恋人身上,温柔细腻。在秋风的轻拂与树林的相和下,傍晚的操场溢满交响乐,煞是动听。</p><p> 良久,太阳已经隐却在地平线下,夜色开始笼罩在世界之上。</p><p> 飘零的落叶是橙红的,落日的余晖是橘黄的,沁人的秋风是透明的,而恋人的爱是多彩的。</p><p> 两只小鸟乘风飞向天空,红喙小鸟用歌喉吟出一首短诗:</p><p> 「黄昏」</p><p> 秋风吹拂着万物</p><p> 恋人俯瞰着世界</p><p> 那树枝奏起的乐曲</p><p> 便是我对你的情思</p><p> </p><p> 无边无尽的凛冬,与高悬空中的皓月。</p><p> 在湖中心的小亭上,只有两只小鸟紧紧相靠。</p><p> 冬季的深夜,清冷难言,清冽的月光倾泻无余。空中有黑夜在翻滚,有寒风在肆虐,地上有恋人在拥吻,有情侣在缠绵。</p><p> 在小亭的长凳,那两只小鸟紧紧地依偎着,以抵御寒风,它们在这无尽的黑夜中,不再叽喳,只是静静地陪伴着彼此。</p><p> 不时有低语传来,使得这深夜愈发静谧。有鸭子在嬉水,传来聒人的叫声。</p><p> 两只小鸟仿佛完全沉浸在自己的世界中,静卧在无边的黑夜里。</p><p> 露珠开始在石凳凝结,唯独无法侵扰这对小鸟。月光流在鸟儿身上,温柔而凛冽。在寒风的玩弄与晚露的扩张下,小鸟依偎得更紧了,它们之间的温度,仿佛篝火一般,煞是令人钦慕。</p><p> 良久,皓月已经隐却在乌云下,夜色愈发清冷难耐。</p><p> 无尽的月夜是黝黑的,清冷的月光是乳白的,远处的路灯是暗黄的,而恋人的爱是多彩的。</p><p> 即将归巢之际,黄喙小鸟叽喳低语:</p><p> 「深夜」</p><p> 凛冬侵蚀着万物</p><p> 恋人超脱于世界</p><p> 那长青不枯的劲松</p><p> 便是我对你的至爱</p><p> </p><p> 无边无尽的绿意,与飘满天空的桐絮。</p><p> 在梧桐絮铺成的毛毯上,许多人在奔走。</p><p> 躁动的暮春,恼人不堪,肆虐的桐絮惹人心生烦闷。</p><p> 在绵延的急躁中,独有两只小鸟在梧桐树上,互相梳理着羽毛,不顾世界的喧嚣。</p><p> 它们起身了,飞向小路的拐角处,这里别有一番春意。</p><p> 桐絮无法飘摇此处,惟有不知何处滴落的水流与爬满墙角的青苔。</p><p> 两只小鸟伫立在墙头,互相望着对方,互相用鸟喙梳理着最为美丽的羽毛。</p><p> 正是中午,阳光有些灼热,春风也变得难以忍受。两只小鸟却依旧在墙头上,仿佛居于尘世之外。</p><p> 良久,暮春的燥热已经隐却在傍晚下,夜色开始爬上树梢。</p><p> 满地的桐絮是黄褐的,墙角的苔痕是青绿的,滴落的流水是透明的,而恋人的爱是多彩的。</p><p> 暮春傍晚的风浮动着,在昏暗的小路尽头,两只小鸟叽叽喳喳地低语:</p><p> 「暮春」</p><p> 桐絮扰动着尘世</p><p> 恋人缔造着世界</p><p> 那细腻不语的青苔</p><p> 便是我对你的爱恋</p>]]></content>
<categories>
<category> 珠玑 </category>
</categories>
<tags>
<tag> 散文 </tag>
</tags>
</entry>
<entry>
<title>Windows下安装WSL 2</title>
<link href="/posts/2023/09/11/tutorial/Install-WSL2-on-Windows/"/>
<url>/posts/2023/09/11/tutorial/Install-WSL2-on-Windows/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="一、准备工作"><a href="#一、准备工作" class="headerlink" title="一、准备工作"></a>一、准备工作</h2><h3 id="1-1-系统版本要求"><a href="#1-1-系统版本要求" class="headerlink" title="1.1 系统版本要求"></a>1.1 系统版本要求</h3><ul><li><p>你的电脑必须运行Windows 10版本2004及更高版本(内部版本19041及更高版本)或Windows 11</p><blockquote><p>按住<strong>Windows徽标键+R</strong>,键入<code>winver</code>来查看电脑版本</p></blockquote></li><li><p>根据<a href="https://blogs.vmware.com/workstation/2020/01/vmware-workstation-tech-preview-20h1.html">VMWare官方博客(2020年1月)</a>,新版VMware已经兼容Hyper-V,最低配置如下</p><ul><li>软件要求:电脑运行Windows 10 20H1(内部版本号19041及更高版本)</li><li>硬件要求<ul><li>Intel Haswell 或更新的 CPU</li><li>AMD Bulldozer 或更新的 CPU</li></ul></li></ul><blockquote><p>如果您的VMWare版本与Hyper-V不兼容,那么两者只能选其一,因此如果你已安装VMWare,请卸载VMWare后再尝试安装WSL 2</p></blockquote></li></ul><h3 id="1-2-基本环境要求"><a href="#1-2-基本环境要求" class="headerlink" title="1.2 基本环境要求"></a>1.2 基本环境要求</h3><ul><li><p>在<strong>管理员模式下</strong>PowerShell或Windows命令提示符中输入以下命令以启用<strong>适用于Linux的Windows子系统</strong>可选功能</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dism.exe /online /<span class="built_in">enable-feature</span> /featurename:Microsoft<span class="literal">-Windows-Subsystem-Linux</span> /all /norestart</span><br></pre></td></tr></table></figure></li><li><p>在<strong>管理员模式下</strong>PowerShell或Windows命令提示符中输入以下命令以启用<strong>虚拟机平台</strong>可选功能</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dism.exe /online /<span class="built_in">enable-feature</span> /featurename:VirtualMachinePlatform /all /norestart</span><br></pre></td></tr></table></figure><blockquote><p>如果只使用WSL 1的功能,则仅需开启第一个可选功能;如果需要使用WSL 2的功能,则两个可选功能均需要开启。</p></blockquote></li></ul><h2 id="二、安装"><a href="#二、安装" class="headerlink" title="二、安装"></a>二、安装</h2><h3 id="2-1-Powershell安装(推荐)"><a href="#2-1-Powershell安装(推荐)" class="headerlink" title="2.1 Powershell安装(推荐)"></a>2.1 Powershell安装(推荐)</h3><p>该方式可一键安装WSL及发行版</p><ul><li><p>在<strong>管理员模式下</strong>PowerShell或Windows命令提示符中输入以下命令</p> <figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--install</span> <span class="literal">-d</span> Ubuntu</span><br></pre></td></tr></table></figure><blockquote><p>此命令将进行如下操作</p><ul><li>启用所需的可选组件</li><li>下载最新的Linux内核</li><li><strong>将WSL 2设置为默认值</strong></li><li>并为您安装Linux发行版——<strong>Ubuntu</strong></li></ul></blockquote></li><li><p>重新启动计算机以安装运行WSL</p></li><li><p>首次启动新安装的Linux发行版时,将打开一个控制台窗口,等待发行版安装完毕</p></li><li><p>输入用户名和密码,初始化设置</p></li></ul><h3 id="2-2-Microsoft-Store安装"><a href="#2-2-Microsoft-Store安装" class="headerlink" title="2.2 Microsoft Store安装"></a>2.2 Microsoft Store安装</h3><ul><li>安装WSL:在Microsoft Store中搜索<code>Windows Subsystem for Linux</code></li><li>安装发行版:在Microsoft Store中搜索<code>Ubuntu</code>等发行版的名称</li></ul><h3 id="2-3-安装包安装"><a href="#2-3-安装包安装" class="headerlink" title="2.3 安装包安装"></a>2.3 安装包安装</h3><p>如因网络环境受限,Microsoft Store下载缓慢或无法下载,可以通过安装包安装</p><ul><li><p>获取WSL安装包<code>MicrosoftCorporationII.WindowsSubsystemForLinux_<Version>_neutral_~_8wekyb3d8bbwe.msixbundle</code></p><blockquote><ol><li>在<a href="https://apps.microsoft.com/home?hl=zh-cn&gl=US">微软商店</a>查找要下载的应用</li><li>复制该应用页面的网址链接</li><li>粘贴至<a href="https://store.rg-adguard.net/">https://store.rg-adguard.net/</a>的文本框内,点击<strong>√</strong>解析</li><li>点击列表项下载(后缀名为<code>.msixbundle</code>或<code>.appxbundle</code>)</li></ol></blockquote></li><li><p>同理,获取Ubuntu等发行版的安装包</p></li><li><p>安装方式一:通过PowerShell中安装</p><ul><li>打开<strong>管理员模式下</strong>的PowerShell</li><li>输入命令<code>add-appxpackage <filename>.msixbundle</code></li></ul></li><li><p>安装方式二:通过应用安装管理器安装</p><ul><li>从Microsoft Store下载<strong>应用安装管理器</strong></li><li>直接双击下载的安装包即可安装</li></ul></li></ul><h2 id="三、发行版"><a href="#三、发行版" class="headerlink" title="三、发行版"></a>三、发行版</h2><h3 id="3-1-查看发行版"><a href="#3-1-查看发行版" class="headerlink" title="3.1 查看发行版"></a>3.1 查看发行版</h3><ul><li><p>通过在PowerShell或Windows 命令提示符中输入以下命令查看<strong>可安装的有效分发</strong></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><span class="line">wsl <span class="literal">-l</span> <span class="literal">-o</span></span><br><span class="line">wsl <span class="literal">--list</span> <span class="literal">--online</span></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows 命令提示符中输入以下命令查看<strong>已安装分发的状态和版本</strong></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><span class="line">wsl <span class="literal">-l</span> <span class="literal">-v</span></span><br><span class="line">wsl <span class="literal">--list</span> <span class="literal">--verbose</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="3-2-更改发行版"><a href="#3-2-更改发行版" class="headerlink" title="3.2 更改发行版"></a>3.2 更改发行版</h3><ul><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>安装新分发</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--install</span> <span class="literal">-d</span> <Distribution Name></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>用新分发替换旧分发</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--install</span> <span class="literal">-d</span> <Distribution Name> <Distribution Name></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>设置默认发行版</strong></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><span class="line">wsl <span class="literal">-s</span> <DistributionName></span><br><span class="line">wsl <span class="literal">--setdefault</span> <Distribution Name></span><br></pre></td></tr></table></figure></li></ul><h3 id="3-3-更改版本"><a href="#3-3-更改版本" class="headerlink" title="3.3 更改版本"></a>3.3 更改版本</h3><ul><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>设置默认版本</strong>,其中<code>Version</code>可选<code>1</code>或<code>2</code></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--set-default-version</span> <Version></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>更改某发行版的版本</strong>,其中<code>Version</code>可选<code>1</code>或<code>2</code></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--set-version</span> <Distribution Name> <Version></span><br></pre></td></tr></table></figure></li></ul><h3 id="3-4-关闭、卸载发行版"><a href="#3-4-关闭、卸载发行版" class="headerlink" title="3.4 关闭、卸载发行版"></a>3.4 关闭、卸载发行版</h3><ul><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>关闭所有发行版</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--shutdown</span></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>终止某发行版</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--terminate</span> <Distribution Name></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>注销(卸载)某发行版</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--unregister</span> <Distribution Name></span><br></pre></td></tr></table></figure></li></ul><h3 id="3-5-导入、导出发行版"><a href="#3-5-导入、导出发行版" class="headerlink" title="3.5 导入、导出发行版"></a>3.5 导入、导出发行版</h3><ul><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>导出某发行版</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--export</span> <Distribution Name> <FilePath></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>导入某发行版</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--import</span> <Distribution Name> <InstallLocation> <FilePath> <span class="literal">--version</span> <Version></span><br></pre></td></tr></table></figure></li></ul><h3 id="3-6-更改发行版位置"><a href="#3-6-更改发行版位置" class="headerlink" title="3.6 更改发行版位置"></a>3.6 更改发行版位置</h3><p>WSL的发行版默认存储在C盘,如果想移动至其他磁盘,可按照以下步骤进行(以Ubuntu示例)</p><ul><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><span class="line">wsl <span class="literal">--export</span> Ubuntu D:/export.tar</span><br></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></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--unregister</span> Ubuntu</span><br></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></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--import</span> Ubuntu D:\Ubuntu D:\export.tar <span class="literal">--version</span> <span class="number">2</span></span><br></pre></td></tr></table></figure></li></ul><h2 id="四、其他命令"><a href="#四、其他命令" class="headerlink" title="四、其他命令"></a>四、其他命令</h2><h3 id="4-1-更新WSL"><a href="#4-1-更新WSL" class="headerlink" title="4.1 更新WSL"></a>4.1 更新WSL</h3><ul><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>更新WSL</strong></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><span class="line">wsl <span class="literal">--update</span></span><br><span class="line">wsl <span class="literal">--update</span> <span class="literal">--web-download</span> <span class="comment">#从GitHub下载</span></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>查看WSL的配置</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--status</span></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>查看WSL的版本</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--version</span></span><br></pre></td></tr></table></figure></li><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>查看WSL命令列表</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--help</span></span><br></pre></td></tr></table></figure></li></ul><h3 id="4-2-用户"><a href="#4-2-用户" class="headerlink" title="4.2 用户"></a>4.2 用户</h3><ul><li><p>通过在PowerShell或Windows命令提示符中输入以下命令<strong>设置某发行版的默认用户</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><Distribution Name> config <span class="literal">--default-user</span> <Username></span><br></pre></td></tr></table></figure></li></ul><h3 id="4-3-挂载"><a href="#4-3-挂载" class="headerlink" title="4.3 挂载"></a>4.3 挂载</h3><ul><li><p>通过在文件资源管理器的地址栏输入<code>\\wsl$\<Distribution Name></code>以下地址<strong>访问WSL文件夹</strong></p><blockquote><p>该功能仅在运行Windows 11版本22000或更高版本的电脑上生效</p></blockquote></li></ul><h3 id="4-4-Linux指令"><a href="#4-4-Linux指令" class="headerlink" title="4.4 Linux指令"></a>4.4 Linux指令</h3><ul><li><p>设置Root密码</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><span class="line">sudo passwd root</span><br><span class="line">su</span><br></pre></td></tr></table></figure></li></ul><h2 id="五、参考"><a href="#五、参考" class="headerlink" title="五、参考"></a>五、参考</h2><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="https://learn.microsoft.com/zh-cn/windows/wsl/"> <div class="tag-link-tips">引用站外地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://www.microsoft.com/favicon.ico);"></div> <div class="tag-link-right"> <div class="tag-link-title">WSL官方文档</div> <div class="tag-link-sitename"> 更多有关WSL安装图形化应用、WSL指令集的内容</div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="https://zhuanlan.zhihu.com/p/224753478"> <div class="tag-link-tips">引用站外地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://static.zhihu.com/heifetz/assets/apple-touch-icon-152.81060cab.png);"></div> <div class="tag-link-right"> <div class="tag-link-title">Winux之路-WSL 2的使用及填坑</div> <div class="tag-link-sitename"> 有关ADB调试、Docker支持、兼容性的问题</div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div>]]></content>
<categories>
<category> 教程 </category>
</categories>
<tags>
<tag> 安装教程 </tag>
<tag> 环境配置 </tag>
<tag> WSL </tag>
</tags>
</entry>
<entry>
<title>编译原理笔记</title>
<link href="/posts/2023/07/24/notes/Compilation-Principle/"/>
<url>/posts/2023/07/24/notes/Compilation-Principle/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="大纲"><a href="#大纲" class="headerlink" title="大纲"></a>大纲</h2><ul><li>引论<ul><li>前后端分离的意义</li><li>编译每个阶段做了什么、编译程序的结构</li></ul></li><li>文法与语言<ul><li>如何设计上下文无关文法</li><li>文法类型</li><li>规范推导、规范归约、规范句型、右句型</li></ul></li><li>词法分析<ul><li>正规文法就是右线性$3$型文法</li><li>$DFA$、$NFA$都是一个初始状态、多个结束状态</li><li>$NFA$到$DFA$的子集法转换、$DFA$如何最小化</li><li>正规文法、正规式、有穷自动机之间的转换</li><li>由正规文法转换到$NFA$时先删除无用产生式</li></ul></li><li>$LL(1)$<ul><li>$FOLLOW$集的求解</li><li>如何提取公因子、如何消除左递归</li><li>如何构造分析表</li><li><strong><u>产生式推导时压栈的方向</u></strong></li></ul></li><li>$LR(1)$<ul><li>如何构造识别活前缀的$NFA$(<u><strong>拓广文法、什么是使用LR(0)项目构造NFA、哪个是acc状态</strong></u>)</li><li>分析表怎么画、有几个栈、发生移进或归约时怎么做、遇到空规则归约怎么办</li><li>什么是$LR(0)$、$SLR(1)$、$LR(1)$、$LALR(1)$文法</li><li>合并$LR(1)$同心项目集,若仍无冲突⟹$LALR(1)$</li><li>如何构造展望符(注意是$\beta\alpha$,注意合并,注意继承)</li></ul></li><li>语义计算<ul><li>什么是综合属性、继承属性,分别在哪里计算,在语法树中表现为什么</li><li>如何构造产生式的语义规则</li><li>如何进行中间代码生成</li></ul></li><li>代码优化<ul><li>如何求必经结点集、回边、自然循环、基本归纳变量、同族的归纳变量</li><li><u><strong>删除基本归纳变量时注意初始化归纳变量</strong></u></li><li><u><strong>进行数据流分析时注意使用最新的值</strong></u></li><li>什么是$UD$链、$DU$链</li></ul></li></ul><h2 id="一、引论"><a href="#一、引论" class="headerlink" title="一、引论"></a>一、引论</h2><ul><li><p>名词释义</p><ul><li>编译程序<ul><li>一个语言的翻译程序(将源程序翻译成目标程序的程序)</li><li>先翻译后执行,一次翻译,多次执行</li></ul></li><li>解释程序<ul><li>翻译程序的一种</li><li>边翻译边执行,翻译一句执行一句,一次翻译,一次执行</li><li>每次执行都离不开解释程序</li></ul></li></ul></li><li><p>编译过程</p><img src="https://picbed.cloudchewie.com/blog/post/Compilation-Principle/Chp0-%E7%BC%96%E8%AF%91%E8%BF%87%E7%A8%8B.png" class="" width="400" title="编译过程"><ul><li>前端:与源语言有关,与目标机器无关</li><li>后端:与目标机器有关,与源语言无关</li><li>词法分析<ul><li>扫描源程序,将源程序识别出一个一个单词($token$)</li><li>分析结果是二元组:($token$的类型,$token$的值)</li><li>相关错误:单词不符合标准或无法识别,如非法标识符,非法数字(整数、浮点数)</li></ul></li><li>语法分析($Parsing$、语法检查、归约、推导)<ul><li>将$token$串,转换成体现语法规则的树状数据结构,叫做抽象语法树($AST$)</li><li>相关错误:$else$没有匹配的$if$</li></ul></li><li>语义分析<ul><li>根据上下文,分析每个单词的意义,并收集必要的信息如类型、目标地址</li><li>相关错误:函数名、数组名混用,$int$和$float$混用,变量重复声明函数调用的参数不对应,$float$类型当下标,数组下标越界,函数未定义,变量未声明</li></ul></li><li>中间代码生成<ul><li>根据语法分析推导出三地址码$TAC$</li></ul></li><li>代码优化<ul><li>优化中间代码,使得占用空间更少、运行速度更快</li></ul></li><li>目标代码生成<ul><li>生成依赖于特定机器的汇编代码</li></ul></li><li>符号表管理<ul><li>记录源程序中使用的各种符号名称</li><li>收集符号的属性信息,类型、作用域、分配存储信息</li><li>登录:扫描到说明语句就将标识符登记在符号表中</li><li>查找:在执行语句查找标识符的属性,判断语义是否正确</li></ul></li><li>错误处理<ul><li>报告出错信息,排错</li><li>恢复编译工作</li></ul></li></ul></li><li><p>编译程序的结构</p><img src="https://picbed.cloudchewie.com/blog/post/Compilation-Principle/Chp1-%E7%BC%96%E8%AF%91%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84.png" class="" width="400" title="编译系统结构"></li></ul><h2 id="二、文法与语言"><a href="#二、文法与语言" class="headerlink" title="二、文法与语言"></a>二、文法与语言</h2><ul><li><p>名词释义</p><ul><li>字母表<ul><li>字母表$∑$是非空有穷集合,其元素称为符号</li></ul></li><li>符号串<ul><li>由字母表$∑$中的符号组成的有穷序列,称为 (字母表$∑$上的)符号串</li><li>不含任何符号的有穷序列称为空串,记为$ε$</li><li>符号串长度记为$\vert \alpha\vert$,且$\vert ε\vert=0$</li><li>运算:连接、或、方幂、正闭包、星闭包</li></ul></li><li>符号串集合<ul><li>如果集合$A$的元素都是字母表$∑$上的符号串,则称集合$A$为$∑$上的符号串集合,简称<strong>串集</strong></li><li>运算:乘积、和、方幂、正闭包、星闭包</li><li>若$A$为任一字母表,则$A^{\ast}$是该字母表上的所有符号串(含空串)的集合</li></ul></li></ul></li><li><p>文法</p><ul><li><p>文法$G$定义为四元组$G=(V_N,V_T,P,S)$</p><ul><li>$V_N$为非空有穷的非终结符集合</li><li>$V_T$为有穷的终结符集合</li><li>$V=V_N\cup V_T$称为文法的字母表,且$V_N\cap V_T=\Phi$</li><li>$P$是非空有穷的规则集合(产生式集合)</li><li>$S\in V_N$,称为文法的起始符</li></ul></li><li><p>直接推导与直接归约</p><ul><li>由$P$中的规则直接进行的串推导称为直接推导或一步推导</li><li>由$P$中的规则直接进行的串归约称为直接归约或一步归约</li></ul></li><li><p>多步推导与多步规约</p><ul><li>由串$\alpha$经过$n$步推导出$\beta$,记作$\alpha\xrightarrow{+}\beta$称之为多步推导</li><li>相应地有多步归约</li></ul><blockquote><p>直接推导与多步推导可以记为$\alpha\xrightarrow{\ast}\beta$</p></blockquote></li><li><p>相关定义</p><ul><li>句型:如果$G[S]$满足$S\xrightarrow{\ast}\beta$,则$\beta$为$G[S]$的句型</li><li>句子:如果$\beta\in V_T^{\ast}$,则$\beta$为$G[S]$的句子;句子也是句型</li><li>语言:文法$G$的句子集合,记为$L(G)$</li><li>文法等价:如果$L(G_1)=L(G_2)$,则$G_1$与$G_2$等价</li></ul></li></ul></li><li><p>文法的类型(乔姆斯分类法)</p><ul><li>$0$型文法(短语文法、图灵机):规则左部至少含有一个非终结符</li><li>$1$型文法(上下文有关文法、线性有限自动机):规则左部至少含有一个非终结符;规则左部长度不大于右部长度(空规则除外)</li><li>$2$型文法(上下文无关文法):规则左部均为一个非终结符</li><li>$3$型文法(正则文法、正规文法、有限自动机)<ul><li>右线性文法:$A\rightarrow\varepsilon\vert a\vert aB$</li><li>左线性文法:$A\rightarrow\varepsilon\vert a\vert Ba$</li></ul></li></ul></li><li><p>语法树</p><ul><li><p>最左推导:任何一步$a\Rightarrow b$都是对$a$中的最左非终结符进行替换</p></li><li><p>最右推导(规范推导):任何一步$a\Rightarrow b$都是对$a$中的最右非终结符进行替换</p><blockquote><ol><li>由规范推导所得的句型,称为规范句型(右句型)</li><li>规范推导的逆过程,叫做规范归约</li></ol></blockquote></li><li><p>语法树满足下列条件</p><ul><li>文法的开始符$S$为树的根节点</li><li>对任意产生式$A→α,α∈(V_N∪V_T)^∗$,$α$的各符号严格依生产式的次序依次为$A$的子节点</li><li>非叶子结点一定是非终结符</li><li>全部叶子结点组成的符号串从左往右读就是文法的句子</li></ul></li><li><p>如果文法$G$的某个句子存至少两棵不同的语法树,则称文法$G$是<strong>二义性</strong>的</p></li></ul></li><li><p>句型分析</p><ul><li>符号串$α$是否符合语言$L$的语法问题,被等价地转化成<u>推导或归约问题</u></li><li>推导法还是归约法?自顶向下还是自底向上?</li><li>自顶向下分析:最左推导,穷举规则</li><li>自底向上分析:移进归约</li><li>短语:语法树中每个内部节点对应一个子语法树,每个子语法树的所有叶节点构成短语</li><li>直接短语:若内部节点的子节点均为叶节点,则该内部节点对应的短语为直接短语</li><li>句柄:最左的直接短语</li></ul></li></ul><h2 id="三、词法分析"><a href="#三、词法分析" class="headerlink" title="三、词法分析"></a>三、词法分析</h2><ul><li><p>词法分析器</p><ul><li>输入:源程序</li><li>输出:($token$的类型,$token$的值)</li><li>$token$的类型:关键字、标识符、字面量(常量)、运算符、界符</li></ul></li><li><p>词法分析的表达方式</p><ul><li><p>正规文法:即右线性$3$型文法</p></li><li><p>正规式</p><ul><li><p>$𝜀$和$𝜙$都是正规式,其正规集分别是${ε}$和$𝜙$</p></li><li><p>$∀𝑎∈∑$,则$𝑎$是$∑$上的正规式,其正规集为${a}$</p></li><li><p>如果$r$和$s$都是$∑$上的正规式,则(算符优先级由高到低 :*,·,|)</p><ul><li>$(r)$是正规式,它表示的正规集为$L(r)$</li><li>$r|s$是正规式,它表示的正规集为$L(r)∪L(s)$</li><li>$r·s$是正规式,它表示的正规集为$L(r)·L(s)$</li><li>$r^{\ast}$是正规式,它表示的正规集为$L(r^{\ast})=L(r)^{\ast}$</li></ul></li><li><p>若两个正规式所表示的正规集相同,则称这两个正规式等价</p></li><li><p>如果正规式$r$和文法$G$,有$L(r)$=$L(G)$则称正规式$r$和文法$G$是等价的</p></li></ul><blockquote><p>正规式又被称为正则表达式</p></blockquote></li><li><p>有穷自动机</p><ul><li><p>不确定的有穷自动机($NFA$):输入符号包括$ε$,一个符号可以标记在离开同一个状态的多条边上</p></li><li><p>确定的有穷自动机($DFA$):输入符号不含$ε$,每个状态以及每个符号,最多只有一条边</p></li><li><p>$DFA$</p><ul><li><p>由$M=(K,\Sigma,f,S,Z)$表述</p></li><li><p>$f$指状态转移函数集合,为$K\times\Sigma\rightarrow K$的单值部分映射</p><blockquote><p>$f(k_i,a)=k_j$表示:当当前状态为$k_i$,输入字符为$a$时,将状态转换到下一状态$k_j$,$k_j$称为$k_i$的一个后继状态</p></blockquote></li><li><p>以箭头指向的状态为起始状态(只有一个),双圈状态为结束状态(可有多个)</p></li></ul></li><li><p>$NFA$</p><ul><li>$f$指状态转移函数集合,为$K\times(\Sigma\cup{\epsilon})\rightarrow P(K)$的单值部分映射,其中$P(K)$表示$K$的幂集</li><li>如果$α\in\Sigma^{\ast}$,$f’(S,α)∩Z≠Φ$,则称符号串$α$是$NFA M$所接受的</li></ul></li><li><p>$NFA$到$DFA$的转换——子集法</p><ul><li>若对于$NFA M$有$I\subset K,α\in\Sigma^{\ast}$,则$Move(I,a)=\displaystyle\cup_{q\in I}f(q,a)$</li><li>定义$\epsilon-Closure(I)\cup=Move(\epsilon-Closure(I),\epsilon)$</li><li>以$\epsilon-Closure({s_0})$为初始状态$S_0$,计算$\epsilon-Closure(Move({s_0},z))$,如果该状态不在状态集中则添加之</li><li>循环为每个新状态进行以上操作,即可得到$DFA$的状态集与转换函数</li><li>在包含初始状态的前面标记$\rightarrow$,在包含结束状态的前面标记$*$</li></ul></li><li><p>$DFA$的最小化</p><ul><li>删除不可达,没有通路到达的状态</li><li>合并等价状态——分割法<ul><li>状态被分成不同子集,不同的子集不等价,同一子集等价</li><li>首先将结束状态放置在同一集合中,其他状态在同一集合中</li><li>判断状态集中与众不同的状态,单独拿出</li></ul></li></ul></li></ul></li></ul></li><li><p>正规式转换为正规文法</p></li></ul><table><thead><tr><th align="center"></th><th align="center">正规式产生式</th><th align="center">文法产生式</th></tr></thead><tbody><tr><td align="center">规则$1$</td><td align="center">$A\rightarrow xy$</td><td align="center">$A\rightarrow xB,B\rightarrow y$</td></tr><tr><td align="center">规则$2$</td><td align="center">$A\rightarrow x^{\ast}y$</td><td align="center">$A\rightarrow xB,A\rightarrow y,B\rightarrow xB,B\rightarrow y$</td></tr><tr><td align="center">规则$3$</td><td align="center">$A\rightarrow x\vert y$</td><td align="center">$A\rightarrow x,A\rightarrow y$</td></tr></tbody></table><ul><li>正规文法转换为正规式</li></ul><table><thead><tr><th align="center"></th><th align="center">文法产生式</th><th align="center">正规式产生式</th></tr></thead><tbody><tr><td align="center">规则$1$</td><td align="center">$A\rightarrow xB,B\rightarrow y$</td><td align="center">$A\rightarrow xy$</td></tr><tr><td align="center">规则$2$</td><td align="center">$A\rightarrow xA\vert y$</td><td align="center">$A\rightarrow x^{\ast}y$</td></tr><tr><td align="center">规则$3$</td><td align="center">$A\rightarrow x$,$A\rightarrow y$</td><td align="center">$A\rightarrow x\vert y$</td></tr></tbody></table><ul><li><p>有穷自动机转换到正规式</p><ul><li><p>每个$NFA$一定有一个等价的正规式,反之也成立</p></li><li><p>新增$X$和$Y$分别作为开始状态和接收状态,$X$经$\epsilon$指向$M$的所有开始状态,将$M$的所有结束状态经$\epsilon$指向$Y$</p></li><li><p>反复应用以下规则,直到只剩下$X$和$Y$为止,这时$X\rightarrow Y$上的式子即为正规式</p><img src="https://picbed.cloudchewie.com/blog/post/Compilation-Principle/Chp2-NFA%E5%88%B0%E6%AD%A3%E8%A7%84%E5%BC%8F.png" class="" width="400" title="NFA到正规式"></li></ul></li><li><p>正规式转换到有穷自动机</p><ul><li><p>新增$X$和$Y$分别作为开始状态和接收状态,$X\rightarrow Y$上的式子为正规式</p><img src="https://picbed.cloudchewie.com/blog/post/Compilation-Principle/Chp3-%E6%AD%A3%E8%A7%84%E5%BC%8F%E5%88%B0NFA.png" class="" width="400" title="正规式到NFA"></li><li><p>直到每条弧上只有单个符号</p></li></ul></li><li><p>正规文法转换到有穷自动机</p><ul><li>$NFA$的字母表即为$G$的终结符</li><li>$M$的每个状态即为$G$的非终结符</li><li>$M$的开始状态$S$即为$G$的开始符号$S$</li><li>增加状态$Z$,作为$M$的结束状态</li><li>$M$的状态转移函数$f$<ul><li>如果$A→ a∈P$ ,则$f (A, a) = Z$</li><li>如果$A→ε ∈P$,则$f (A, ε) = Z$</li><li>如果$A→aB∈P$,则$f (A, a) = B$</li></ul></li></ul></li><li><p>有穷自动机转换到正规文法</p><ul><li>先将$NFA$确定化为$DFA$</li><li>如果$f (A, a) = B$,则$A→aB$</li><li>对结束状态$Z$,增加$Z\rightarrow\epsilon$</li><li>如果$f(B,a)=C$,$C$为结束状态,且非始态,又无出边,则$B→a$</li></ul></li></ul><h2 id="四、-LL-1-语法分析"><a href="#四、-LL-1-语法分析" class="headerlink" title="四、$LL(1)$语法分析"></a>四、$LL(1)$语法分析</h2><ul><li><p>$LL(1)$的判定:若对于任意左部相同的产生式,其不同产生式满足$SELECT$集两两不相交,则是$LL(1)$文法,其中右部不能同时推出$\epsilon$</p><ul><li><p>$FIRST$集</p><ul><li>参数为串$\alpha$(可以包括终结符和非终结符)</li><li>如果串$\alpha$能够推导得到$\epsilon$,则$\epsilon \in FIRST(\alpha)$</li></ul></li><li><p>$FOLLOW$集</p><ul><li>参数为非终结符</li><li>满足#$\in FOLLOW(S)$</li><li>若$S\xrightarrow{\ast}\cdots A$,则#$\in FOLLOW(A)$</li><li>若$S\xrightarrow{\ast}\alpha A\beta$,$\beta\xrightarrow{\ast}\epsilon$,则#$\in FOLLOW(A)$</li><li>若$A\xrightarrow{\ast}\cdots B$或者$A\xrightarrow{\ast}\cdots B\beta$,$\beta\xrightarrow{\ast}\epsilon$,则$FOLLOW(A)\subseteq FOLLOW(B)$,$FOLLOW(B)\cup= FOLLOW(A)$</li></ul></li><li><p>$SELECT$集</p><ul><li><p>参数为产生式$A\rightarrow\alpha$</p><p>$$SELECT(A\rightarrow\alpha)=\begin{cases}<br> FIRST(\alpha) & a\not\xrightarrow{\ast}\epsilon \\[2ex]<br> (FISRT(\alpha)-\epsilon)\cup FOLLOW(A) & a\xrightarrow{\ast}\epsilon<br>\end{cases}$$</p></li><li><p>$SELECT$集是终结符号集$V_T$的子集</p></li></ul></li></ul></li><li><p>$LL(1)$的构造</p><ul><li><p>提取左公因子</p><ul><li>对形如$A→αβ_1\vert αβ_2\vert \cdots\vert αβ_n$进行等价变换为(引入新非终结符$A’$)<ul><li>$A→αA’$</li><li>$A’→ β_1\vert β_2\vert \cdots\vert β_n$</li></ul></li></ul></li><li><p>消除左递归</p><ul><li><p>直接递归</p><ul><li>形如$A→αAβ$的规则称为文法$G$的直接递归规则</li><li>特别地,如果$α=ε$时,则称为文法$G$的直接左递归规则</li><li>如果$β=ε$时,则称为文法$G$的直接右递归规则</li></ul></li><li><p>间接递归</p><ul><li>如果存在推导$A\rightarrow \alpha\xrightarrow{\ast}\lambda A\mu$,则规则$A→α$称为文法$G$的间接递归规则</li><li>特别地,如果$λ=ε$时,则称为文法$G$的间接左递归规则</li><li>如果$μ=ε$时,则称为文法$G$的间接右递归规则</li></ul></li><li><p>消除直接左递归</p><ul><li><p>对形如$A→Aα_1\vert Aα_2\vert \cdots\vert Aα_m\vert β_1\vert β_2\vert \cdots\vert β_n$等价变换为</p><ul><li>$A’→α_1A’\vert α_2A’\vert \cdots\vert α_mA’$</li><li>$A→ β_1A’\vert β_2A’\vert \cdots\vert β_nA’$</li><li>$A’→ε$</li></ul></li></ul></li><li><p>消除间接左递归</p><ul><li>对非终结符进行排序,将前面的非终结符的产生式代入后面的终结符的产生式</li></ul></li></ul></li></ul></li><li><p>$LL(1)$的分析</p><ul><li><p>递归下降子程序法</p><ul><li>将每个非终结符(比如$A$)编写成一个递归子程序($parseA$),完成选择规则、推导和匹配的功能</li><li>若非终结符$A$有多个产生式,则根据产生式的$SELECT$集选择相应的规则推导,对规则$A\rightarrow Y_1Y_2\cdots Y_n$,依次调用右部非终结符的子程序或匹配终结符,$Y_i$为非终结符则调用$Y_i$对应的子程序,若$Y_i$为终结符,则与当前$token$匹配。</li><li>匹配成功则调用词法分析程序取下一个$token$,匹配失败或当前$token$不属于任何$SELECT$集时,报告语法错误</li><li>按照递归子程序法构造的语法分析程序是由一个总控子程序和一组非终结符对应的递归子程序组成的</li></ul></li><li><p>表预测分析法</p><ul><li>分析表的横轴是终结符,纵轴是非终结符,表项根据$SELECT$集填写</li><li>分析栈$S$:初始状态为#S,由于是最左推导,每次根据产生式推导时从右至左入栈</li><li>输入栈$I$:初始状态为w#</li></ul></li></ul></li></ul><h2 id="五、-LR-语法分析"><a href="#五、-LR-语法分析" class="headerlink" title="五、$LR$语法分析"></a>五、$LR$语法分析</h2><ul><li>$LR(0)$<ul><li>构造识别活前缀的$DFA$<ul><li>添加$S’\rightarrow S$</li><li>每个规则$A\rightarrow\alpha$构造一个等价的$NFAMA\rightarrow \alpha$</li><li>合并所有$NFAMA\rightarrow \alpha$,得到$NFAM$</li><li>确定化$NFA$得到$DFA$</li></ul></li><li>化简<ul><li>将$NFA$中的状态替换为产生式的不同形式</li><li>将同一个子集的状态写在一起,作为新的状态,得到移进项目、待约项目、归约项目、接受项目</li><li>根据$DFA$构造分析表</li></ul></li></ul></li><li>$SLR(1)$<ul><li>在构建分析表遇到归约项目$A\rightarrow \alpha\cdot$时,只有当非终结符在$FOLLOW(A)$中才填入$r_i$</li></ul></li><li>$LR(1)$<ul><li>为每个状态添加展望符</li></ul></li><li>$LALR(1)$<ul><li>合并同心集</li></ul></li></ul><h2 id="六、语义计算"><a href="#六、语义计算" class="headerlink" title="六、语义计算"></a>六、语义计算</h2><h2 id="七、运行时存储组织"><a href="#七、运行时存储组织" class="headerlink" title="七、运行时存储组织"></a>七、运行时存储组织</h2><ul><li><p>概述</p><ul><li>编译程序将源程序的<u>算法描述</u>部分和数据说明部分,分别翻译成<u>机器目标代码</u>和<u>数据存储单元</u>,最终获得目标程序</li><li>浮动地址代码:源程序的对象地址分配往往是<u>相对于运行存储空间的偏移量</u>,对象访问采用“基地址+偏移量”寻址方式进行,使得可以选择内存的任意可用区域作为目标程序运行时的存储区</li><li>数据对象的表示</li><li>表达式计算</li><li>存储分配策略<ul><li>静态存储分配:对于所有数据对象,其分配的存储地址都是相对于静态数据区的偏移量</li><li>栈式存储分配:在编译过程中,对于所有数据对象,其分配的存储地址都是相对于数据对象所在的<u>子程序</u>数据区的偏移量</li><li>堆式存储分配:允许运行期间自由申请与释放存储空间,<strong>当过程/函数结束后,数据对象仍然可以长期存在</strong></li></ul></li></ul></li><li><p>活动记录</p><ul><li><p>栈帧($frame$)/过程活动记录($AR$):用以存放过程的一次执行所需要信息的一段连续的存储区称为过程活动记录</p><img src="https://picbed.cloudchewie.com/blog/post/Compilation-Principle/Chp4-%E6%B4%BB%E5%8A%A8%E8%AE%B0%E5%BD%95.png" class="" width="400" title="活动记录"></li><li><p>访问链指向其它过程活动记录的指针单元,用于访问分配在其它过程活动记录的非局部变量</p></li><li><p>控制链指向调用本过程的过程活动记录的指针单元</p></li><li><p>当调用子程序时,在数据空间栈顶,给子程序分配所需的子程序过程活动记录</p></li><li><p>当子程序返回时,从数据空间栈顶,收回分配给子程序所占用过程活动记录</p></li><li><p>在允许递归调用时,一个子程序可能在数据空间中同时拥有多个过程活动记录</p></li></ul></li><li><p>嵌套层次表</p><ul><li>嵌套层次表$display$<ul><li>一个以静态层次数$i$为下标的、指针类型的一维数组$display$</li><li>$display[i]$用于指向静态层次数为$i$的过程活动记录之基地址</li></ul></li><li>在静态存储区维护一个全局$display$表</li><li>每个子程序的活动记录$AR$均拥有自己的嵌套层次表$display$<ul><li>重复调用某个子程序时,$display$表对应静态层数的值更新为最新调用的过程活动记录的基地址</li><li>子程序中访问外层变量的绝对地址是:绝对地址=$display[i]$ +偏移量</li><li>保存整个$display$表:假定被调用者子程序的静态层次数为$i$,建立其$display$的具体做法是:复制调用者子程序的$display[0]\sim display[i-1]$ ,$display[i]$指向自己(被调用者)的活动记录的基地址</li><li>保存一个$display$表项:假定被调用者子程序的静态层次数为$i$,该表项存储$D[i]$先前的值,如果先前没有定义,则为_</li></ul></li><li>静态链</li></ul></li><li><p>过程调用</p><ul><li>子程序之间的数据交换是通过<u>变量作用域</u>或<u>参数传递</u>的方法进行的</li><li>传值($call-by-value$):将实参表达式的数据值,复制到对应的形参单元;形参属于子程序的局部变量,在子程序中,对其访问采用<u>直接寻址</u>方式</li><li>传址($call-by-reference$):将指针型实参表达式的<u>地址</u>值,复制到对应的形参单元。形参属于子程序的局部变量,在子程序中,对其访问采用<u>间接寻址</u>方式</li></ul></li></ul><h2 id="八、代码优化"><a href="#八、代码优化" class="headerlink" title="八、代码优化"></a>八、代码优化</h2><ul><li><p>常用优化技术</p><ul><li>常量折叠、常量传播、稀疏有条件的常量传播</li><li>代数化简、强度削弱</li><li>复写传播、值编号、公共子表达式删除、部分冗余消除</li><li>向量计算</li><li>循环不变代码外提、归纳变量强度削弱、归纳变量删除、边界检查消除、循环展开、循环重组</li><li>尾递归删除</li></ul></li><li><p>控制流优化</p><ul><li>不可达代码消除、死代码删除、拉直、反分支</li><li>基本块<ul><li>确定入口:程序的第$1$条语句;跳转语句的跳转目标语句;条件跳转语句的下一条语句;</li><li>每个入口对应一个基本块:从入口语句直到下一个入口语句之前</li><li>凡不属于任何一个基本块的语句都是<strong>不可达</strong>语句,将其全部删除</li></ul></li><li>流图<ul><li>流图的结点是一些基本块</li><li>从基本块$B$到基本块$C$之间有一条边当且仅当基本块$C$的第一个指令可能紧跟在$B$的最后一条指令之后执行——有一个从$B$的结尾跳转到$C$的开头的条件或无条件跳转语句</li></ul></li><li>必经结点集$D(n)$<ul><li>初始化:$D(1)={1},D(2)=D(3)=\cdots=D(n)={1,2,3,4,5,6,\cdots,n}$</li><li>计算:$D(i)={i}\cup(\cap D(p)\vert p\in Pred(n))$</li><li>重复计算直到$D(n)$不再变化</li></ul></li><li>回边:$n\rightarrow m$,且$m\space DOM\space n$,则称$n\rightarrow m$是<strong>回边</strong></li><li>自然循环:给定一个回边$n\rightarrow d$,该回边的自然循环为:$d$,以及所有可以不经过$d$而到达$n$的结点——循环加入前驱节点</li><li>优化方法<ul><li>如何识别删除公共子表达式</li><li>如何识别无用代码</li><li>如何自动识别循环不变计算</li><li>如何进行归纳变量的强度削弱和删除</li></ul></li></ul></li><li><p>数据流分析</p><ul><li><p>到达定值分析</p><ul><li>存在一条从定值$d$到程序点$p$的路径,且在此路径上$d$没有被“杀死”,则称定值$d$到达程序点$p$</li><li>计算各入口的定值,根据入口处定值推算出口处的定值</li><li>初始:$out[ENTRY] = \emptyset$,每个基本块$out[B] = \emptyset$</li><li>计算:某个$out$值发生变化,即每个初始块重新计算$in$和$out$<ul><li>$in[B] = ∪_{P∈pred(B)} out[P]$</li><li>$out[B] = gen_B ∪(int[B] - kill_B)$</li></ul></li><li>重复计算,直到$out$值不再变化</li></ul></li><li><p>$UD$链:点$u$引用$A$, 能到达点$u$的$A$的所有定值点的全体称为$A$在点$u$的引用-定值链</p></li><li><p>活跃变量分析</p><ul><li>如果存在某个点引用了某个变量(要求在重新定值/杀死之前被引用),则称该变量是活跃的</li><li>计算各基本块出口的活跃变量,根据出口处活跃变量推算入口的活跃变量</li><li>$in[B]=use_B\cup(out[B]-def_B)$<ul><li>$use_B$:$B$中引用前未定值的变量的集合(已知量)</li><li>$def_B$:$B$中定值前未引用的变量集合(已知量)</li></ul></li><li>初始:$in[EXIT] = \emptyset$</li><li>计算:某个$in$值发生变化,即每个初始块重新计算$in$和$out$<ul><li>$out[B] = U_{S\in succ(B)}in[S]$</li><li>$in[B]=use_B\cup(out[B]-def_B)$</li></ul></li><li>重复计算,直到$in$值不再变化</li></ul></li><li><p>$DU$链:设变量$x$有一个定值$d$,该定值所有能够到达的引用$u$的集合称为$x$在$d$处的定值-引用链</p></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 编译原理 </tag>
</tags>
</entry>
<entry>
<title>凸凹</title>
<link href="/posts/2023/07/17/essay/Concave/"/>
<url>/posts/2023/07/17/essay/Concave/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><p> 最初,万物都在洞穴里,处在同一个时空中,过着相同的生活。然而,某种东西的扩张却撕裂了它,于是,一切注定了他们几乎不能正常交流,甚至,是敌对,是驯服,是杀戮,是蚕食。</p><p> “后来,时空的撕裂让万物彼此分离,习性大变,就比如猫会毫不留情地吃掉我们。最后,我们只能缩在这小小的洞穴里,来避免猫的抓捕。”</p><p> 莱恩被祖母的话吓得呆在了那里,从未出去过的他不能想象猫究竟是什么样子的。</p><p> “猫真的是像你说的那样吗?”莱恩望向正在收拾桌子的祖母。</p><p> 莱恩急切地想要知道答案,可是她并没有回答。</p><p> “哲理书里说猫是善良的动物啊,猫怎么会像你说的那样呢?”莱恩不依不饶。</p><p> 祖母放下手中正在整理的残羹,摸了摸莱恩的头。</p><p> “可书都是骗人的啊,孩子。”</p><p> 祖母转过身去,喃喃道:“你的祖父也曾这样啊。”</p><p> </p><p> 周围的一切都是静悄悄的。</p><p> 莱恩捻了根灯芯插进偷来的蜡油中,偌大的洞穴里亮起了一点烛光。</p><p> 他蹑手蹑脚地靠近洞口,跳动的烛光一照,那是用一块破木板裁成的洞门。</p><p> 洞门被很结实地钉在墙上,还有几根沉重的铁木支着。</p><p> 莱恩使出全身的力气移开挡着洞门的铁木。</p><p> 之后,莱恩拍了拍自己的布袋子,里面的东西还在。</p><p> 他回头看了看自己自出生起便从未离开过的洞穴,吹熄了蜡烛。</p><p> 拉开洞门,莱恩愣在了原地。</p><p> 窗外的月光正好洒向洞口,一个崭新的世界在莱恩眼前毕露无疑。</p><p> 就这样,莱恩逃出了那个洞穴。他从没想到会如此简单,却也仍旧是怀着满心的向往和自以为无穷无尽的胆量远别了身后的洞穴。</p><p> </p><p> 莱恩顺着流泻的月光小心翼翼地摸索着。</p><p> 先是冰凉的地板,接着是桌脚,莱恩前进的速度越来越快。他穿过门缝,踩着软绵绵的地毯,经过尚未完全熄灭的壁炉,爬上沙发,最后一跃跳到了他认为的月光源头。</p><p> 寂静的夜晚中,莱恩的心跳显得格外突出。他瘫软在冰冷的窗台上,攥紧了自己的布袋子,慢慢地让自己的心情平复。直到忽然感到身后的墙壁似乎开始发烫,他才缓过神来。</p><p> 莱恩扶着墙慢慢地直起身子来,当看到外面一望无际的草野时,他又瘫倒下来。在这以前,关于洞穴外面的一切,莱恩只是道听途说,却从未如此近地感受过。莱恩打了个趔趄,他害怕自己会掉下去,却被某种东西猛地挡住。莱恩吓得直哆嗦,他当然不知道,那是透明的玻璃。</p><p> 此时正是秋末冬初,外面啊,无尽的都是枯黄的草。在这晴朗的夜晚,寒风趟过去,这草野便变成了银白色的霜。月光跳动在霜上,于是漫山遍野凛然而温润。剔透的世界在天幕下随风凸凹,无穷无尽的波浪远去又归来。</p><p> 不知过了多久,莱恩才将思绪从眼前的景象中剥离出来,他打开自己的布袋子,在几张巨大的残页上找到空白的地方,用指尖蘸着自制的黑墨水,在纸上写下了自己出逃后写的第一首诗——</p><p> 走出洞穴</p><p> </p><p> 天花板倏然间高了许多</p><p> 洞外清冽的光胜过烛火</p><p> 看啊那漫山遍野</p><p> 你还觉得孤单吗</p><p> </p><p> 除此之外,那残页上还有许多错乱歪扭的字,看得出来,那是一首首诗:</p><p> 追忆祖父</p><p> </p><p> 清与浊,该如何取舍</p><p> 追寻与苟活,该如何选择</p><p> 未知的危险是否只是谣传?</p><p> 还是心中杜撰的一个谎言?</p><p> </p><p> 我的哲理体悟</p><p> </p><p> 慈悲不应是个贬义词</p><p> 善良也不应含有偏见</p><p> 是令人窒息的洞穴啊</p><p> 还是危机四伏的洞外</p><p> </p><p> 诗与歌</p><p> </p><p> 若是诗不押韵</p><p> 你还能读懂它吗</p><p> 若是歌不成调</p><p> 你还能领会它吗</p><p> </p><p> 决绝</p><p> </p><p> 谁说时空的撕裂无法弥补</p><p> 慵懒与胆怯终只无济于事</p><p> 打开尘封的那扇门啊</p><p> 无论门外如何 令人心悸</p><p> </p><p> 莱恩做了个梦,梦中,他的影子被清冽的光拉得很长。莱恩的影子旁边还有一个更大的影子。莱恩看着自己身旁的影子像是张开了巨口,慢慢靠近自己的影子,最后莱恩眼睁睁地看着它突然间将自己的影子覆盖住,而当身旁的影子移开时自己的影子已经不见了。空荡荡的,仿佛自己的身体已经消失了。</p><p> 这时,窗外,冬阳慢慢地升起,枯草上凝结着的霜闪闪发光,又逐渐融化成水,最后消失地无影无踪。</p><p> 而当梦中的莱恩想要扭头看自己的旁边是谁时,一声尖叫却将莱恩拉回现实。</p><p> 莱恩下意识的捂紧布袋子。</p><p> 他看到,那是一个庞然大物在指着自己尖叫。</p><p> 莱恩慌乱地跳下了窗台,躲到了沙发后面。周围变得嘈杂起来,他缩了缩身子,尽量把自己藏在沙发下面。许久,周围渐渐沉寂,又过了一会儿,房子里只剩下窸窸窣窣的声音。可莱恩并不敢松懈,他听祖母讲过,祖父出去觅食时也曾被人类逮个正着,之后便再也没有回来。 </p><p> 可是,情况不妙,这窸窸窣窣的声音越来越近,莱恩逐渐听出那是鼻息。鼻息声更大了,莱恩甚至感觉自己听到了两个心跳声。</p><p> 紧接着,沙发突然被挪开,略微炙热的阳光直照在莱恩身上。在他棕灰色的毛发下,血管急剧扩张。耀眼的阳光中,莱恩模糊地看到,一个庞大的生物用一对硕大的眼睛死死盯着自己。莱恩被它柔软的胡须磨蹭着,恐惧感渐渐地攫取他的呼吸,以及他的心跳,最后,莱恩晕了过去。</p><p> 莱恩又做了一个和昨晚相同的梦,他扭头去看身旁是谁时,看到了和自己晕过去前看到的一样庞大的东西,他揉了揉眼睛,极力去看清楚它,渐渐地,他看到了,那是</p><p> ——猫。</p><p> 莱恩被吓醒了,可是当他睁开眼睛时他看到的仍然是——猫。</p><p> 莱恩瘫倒在地,他不知道自己是害怕还是紧张。因为,尽管从小被灌输这种思想,但作为一个从未真正见过猫的而且坚信着哲理书的老鼠,哦,不,也许可以称之为哲理家,他宁愿相信自己被哲理告诉的那样——猫是善良的动物。</p><p> 毫无经验的莱恩也不允许自己的偏见胜过自己的理智。</p><p> 经过这番思考,莱恩终于战胜了自己。现在,他认为,自己之所以面对猫时想要逃跑,是因为一时的紧张。</p><p> “嘿,我想你便是猫吧?”莱恩略显幼稚地问道,企图打破沉闷而尴尬的氛围。</p><p> 可是,猫并不懂他在说什么,只是用爪子挠了挠鼻子。显然,他对一只对着自己叽叽地叫着的老鼠感到可笑。</p><p> 莱恩正对猫的举动感到困惑,忽然间,他看到自己的布袋子被撂在一边,里面的几张残页散乱地落在地上。莱恩恼怒地想要夺回残页,却被猫一掌摁在地上。</p><p> 莱恩被吓坏了,现在他的意识里已经将猫的危险度提高了一个等级。他不断地挣扎着,随之,猫挪了挪前爪,只是把莱恩的尾巴死死按住。</p><p> 这时,莱恩已经可以碰到残页,他迅速把残页紧紧攥到自己手中。</p><p> 猫死死地盯着莱恩。</p><p> 良久,他才试探性地又略带碰运气心理地用爪子蘸着墨水在残页上写道:</p><p> “我知道,猫是善良的动物。”</p><p> 猫却竟也能看懂文字,他喵呜地叫了几声,似乎是在表达惊讶与嘲笑。</p><p> 莱恩继续写着:“想必,你也是一只懂哲理的猫吧?”</p><p> 这的确是个令人毫无头绪的问题。</p><p> 猫没有做出任何的声音回应,却也用笨拙的爪子在木质地板上写起了字。</p><p> “你很像我的爸爸讲的他之前碰到的一只老鼠,一样胆大包天。”</p><p> 莱恩顿时兴奋了起来,猫看起来并不像祖母说的那样危险嘛,至少到现在为止是这样的。</p><p> 莱恩继续写:“你会吃我吗?”</p><p> “当然会,这可是命定的规则,你注定要被我吃掉。”猫轻蔑地笑了笑。</p><p> 莱恩有些惊惧。</p><p> 这时,残页上已经不允许莱恩继续写字了,他只好在地板上写:“你知道吗?其实在故事的开端,我们都在洞穴里,处在同一个时空中,过着相同的生活。但是,某种东西的扩张却撕裂了它,于是,一切注定了我们几乎不能正常交流,甚至,是敌对,是杀戮,是蚕食。” </p><p> 莱恩顿了一会儿,又继续写:“也许就像你和我现在这样。”</p><p> 猫看了看,若有所思,却又忍俊不禁:“即使如此,不管同处一个洞穴的故事荒诞与否,现在规则摆在那,我们之间的关系就必须是这样,而且,你跟我谈这个,不显得很可笑么。”</p><p> 莱恩有些抓狂:“规则,规则,你一直说规则,什么是规则?难道你说的话就是世界的准则吗?”</p><p> 猫看到莱恩的样子,又不禁狠狠地将他摁在地上,用力地在地板上写着:“真没想到,我的食物死到临头了还能如此嚣张。”</p><p> 写完,猫又轻蔑地笑了笑。</p><p> 莱恩对猫盛气凌人的姿态置之不理,转而问起来猫之前说的那只老鼠:“你爸爸碰到的那只老鼠呢?他也是这样吗?你爸爸把它毫不留情地吃了吗?”</p><p> “当然了,我说过了,命定的规则就是这样,谁也无法挽回,谁也不能违背。”猫依旧是一副轻蔑的姿态。</p><p> 听完,莱恩一直怔着,他想到了自己的祖父。</p><p> 他宁愿相信那就是自己的祖父,哪怕自己重蹈覆辙。</p><p> </p><p> 这时,地板上已经写满了各种歪歪扭扭的字。</p><p> 猫一阵乱扑,将尚未完全干透的墨迹擦去,原本褐色的地板颜色变得更深了。</p><p> 接着,猫看着愣着不动的老鼠,在地板上写下一行模模糊糊的字:“不如你学我说话吧。”</p><p> 于是,也许某天,在冬天的第一场雪中,你会在一个周围都是草野的木屋里,听到一只老鼠在喵呜喵呜地叫着。壁炉里的火星不时溅到地板上,又瞬间熄灭,就像屋外的雪砂,落入枯草中瞬间湮灭。</p><p> </p><p> 猫说:“那纸上歪七八扭的都是什么?” </p><p> “我写的诗啊,没人的时候,我都会点着烛灯,把自己的感受和思考写在我捡到的一本哲理书上。可是有一次,烛灯把纸烧着了,最后只剩下了现在的残页。”</p><p> “这也算是诗?”猫小声嘀咕。</p><p> 猫指着“你还觉得孤独吗”,轻蔑地说:“只会满腹牢骚。”</p><p> 莱恩愣了愣,默默地写下一首诗:</p><p> 我是一只住在地洞里的老鼠</p><p> 有天偶然拾到一本哲理书</p><p> 我点燃了尾巴仔细阅读</p><p> 不知不觉之中却被它束缚</p><p> 它告诉我猫是善良的动物</p><p> 在这个世界上其实我很孤独</p><p> 我不胆小,这点像我的祖父</p><p> 我决定追求心中的幸福</p><p> </p><p> 猫感觉自己似乎戳中了莱恩的痛点,他有那么一瞬间,觉得自己不应该吃掉这只老鼠,可是自己又好像没有理由这样做,况且,自己可不能违背规则,也绝不可能违背规则。</p><p> 莱恩的目光不断游离在地板上,最后他慢慢地说:“自从读过那本哲理书,我就开始向往着洞外的生活,想要走出那个幽暗的洞穴去寻找心中的幸福。我不相信猫会如此凶残地,毫不犹豫地吃掉我们,所以我一直被别人称为披着鼠皮的猫,没有人愿意接近我,他们都害怕自己被‘哲理’蛊惑。有一次,我的祖父离开洞穴后,就再也没回来。我想,他不是为了觅食,而是为了去寻找心中的幸福,但可能最终他不幸成为了某只猫的腹中餐吧。”</p><p> 猫无言。</p><p> 接着,莱恩又开始在残页上写东西,可是残页已经写满了,现在他只能在原来的诗上继续写——</p><p> 认识猫</p><p> </p><p> 看过漫山遍野的霜</p><p> 见过肯陪我聊天的猫</p><p> 可是世界的凹凸</p><p> 谁来告诉我</p><p> </p><p> 写完,莱恩问猫:“你会写诗吗?”</p><p> “诗有什么好写的,你写这种东西,纯粹就是无病呻吟的。”</p><p> 莱恩涨红了脸:“你太片面了,你可以赋予诗暗喻,情感,甚至是某种信息。”</p><p> “哦?那大诗人,大哲理家,你告诉我,什么是诗?”猫把他的胡须靠在莱恩棕灰色的面颊上。</p><p> “诗就是不能被翻译的文字,一旦被翻译成其他语言,诗就会失去它原本的含义。”</p><p> “荒谬至极!”猫不屑地回答。</p><p> 莱恩无言。</p><p> “那你那么拼命保护你的布袋子,就是为了保护你写的那几首诗?”</p><p> 莱恩依旧没有回答。</p><p> 沉默了一会儿,莱恩突然冒出来一个问题:“你还会吃掉我吗?”</p><p> 猫不假思索:“为什么不呢?”</p><p> “那什么时候?”</p><p> “这得看我心情,不过你大可以逃掉,我不会拴着你的。”</p><p> 这时已是深夜,窗外的雪簌簌地落着,壁炉里的火星几乎就要消失殆尽。进入冬天越来越久,曾经清冽的光已经很少露面。</p><p> 过了很久,莱恩又突然冒出来一个问题:“你父亲碰到的那只老鼠真的被吃掉了吗?”</p><p> “这有什么值得怀疑的。”</p><p> </p><p> 认识猫后的第三天,莱恩决定要逃走。</p><p> 莱恩问猫:“你想过离开这里吗?”</p><p> “为什么要离开?这世界是环套着环的,当你走出一个洞穴时,就来到了另一个洞穴里,总是走不出去的。” </p><p> “荒谬至极!”</p><p> 猫乜着眼:“我可是个伟大的预言家。”</p><p> “没有终点?”</p><p> “没有,有的话,终点也只会是堵墙。”</p><p> “荒谬至极!”莱恩不满地重复道。</p><p> “那何不走出去看看,看看最终谁是对的。”猫将头撇向窗外。</p><p> 莱恩很是吃惊。</p><p> 这下连逃都不用逃了。</p><p> “你不会吃掉我吗?”</p><p> “当然会,我可是个从不违背规则的人,但我也是有原则的。”</p><p> “等你发现走不出洞穴时,也许你就会乖乖地爬到我的肚子里的。”猫扭头看着莱恩。</p><p> 莱恩没有说话,他翻了翻自己的几张残页,抚摸着上面的文字,许久。</p><p> </p><p> 这是一个大雪初停的早晨。万籁俱寂,壁炉早已熄灭。猫推开了小门,伸出右前爪踏在门外的雪上。莱恩也伸出脑袋探向门外,这是他第一次看到雪,触碰到雪。他抓了一把雪,紧紧握着,好久才松开,可雪早已都化成了水消逝得无影无踪。</p><p> 猫把莱恩绑在自己的项圈上,踏着漫山遍野的雪,出发了。</p><p> 过了很久,回头望去,木屋已经小的还没有莱恩大,一路上的脚印清晰可见。雪又匆匆地落下,整个世界渐渐被世界被雪抚平,没有凸,也没有凹。隆冬的风尤为冷冽,莱恩竭力地缩在猫的身边。不一会儿,猫和莱恩便被大雪淹没在偌大的原野上。</p><p> 就这样,雪下了又停,停了又下,冬天一点一点地溜走。最后不知过了多久,猫和莱恩到了一条小河边上。</p><p> 小河里,冬冰紧紧地护佑着水下的鱼儿,原来冬天也有着温暖啊。</p><p> 猫停下了,他竭力地在冰面上用爪子抓着挠着,可是冰层就是不肯屈服。莱恩扯着绑着他的绳子顺着项圈滑下来,也帮着猫刨冰。</p><p> 最后不知过了多久,冰面才被掏出一个小窟窿。</p><p> 猫累了,莱恩也累了。</p><p> 猫盯着莱恩,说:“说不定,我饿的不行了,也会毫不留情的把你吃掉。”</p><p> 莱恩撇了撇嘴,又去刨冰。</p><p> 又过了不知多久,猫终于从小河里捕出来几条小鱼。</p><p> 可是莱恩并没有东西吃,在洞穴里的时候,他总是分到很少的食物,他已经习惯了没有东西吃。</p><p> 这时候,冬天渐渐地露出了尾巴。</p><p> 猫用爪子刨开残雪,揪出下面长出的嫩嫩的根茎递给莱恩。</p><p> 莱恩先是一愣,紧接着开始大快朵颐。</p><p> 此时此刻,从天空中俯视,我们可以看到,空阔的雪野上,泥土与残雪杂乱地围绕在一大一小两个黑影旁。小河里的冰已经薄了许多,不时有鱼儿顺着冰游动。</p><p> 猫说:“春天不远了。”</p><p> 莱恩应着:“你说,等到春天真正到来的时候,我们能够走出这个洞穴吗?”</p><p> 猫摸了摸饱饱的肚子,漫不经心地回答:“洞穴是很深的,我们现在还在洞穴的深处。” </p><p> </p><p> 当他们沿着小河走到原野边缘的时候,春天已经彻彻底底地来了。</p><p> 前面是光秃秃的白桦林。此时,雪已经消融殆尽,我们可以听到灌木下面的雪水缓缓地流入小河中。</p><p> 不久,春天的第一场雨来了。</p><p> 雨很小,淅淅沥沥的,像是天上有谁在断断续续地哭。</p><p> 白桦林的花芽开始蠢蠢欲动,上一个秋天的落叶下新的生命正在孕育。</p><p> 猫和莱恩继续走着,春雨来了一场又一场。</p><p> 林子里面,慢慢地开出许多奇异的花儿,大得像荷叶一样的叶片长着掌状的脉络,柔软的绒毛蓄满了一颗又一颗的露珠,珍珠似的白色小花在伞形叶的庇佑下簇拥着。</p><p> 猫说,那是骷髅花,他在书上见到过。</p><p> 莱恩不解,这小花并不像骷髅的样子啊。</p><p> 当春雨落在骷髅花上,花儿神奇般地变得透明,像水晶一样,花瓣里的花络清晰可见。</p><p> 猫又言,这花还有个名字,叫冰莲。</p><p> 莱恩又不解。</p><p> “名字并不代表什么,它只是个记号罢了。”猫似乎知道莱恩的疑惑。</p><p> 他们在白桦林里走着,吃浆果,吃蘑菇,这着实委屈了猫。</p><p> 莱恩采了几多花,小心翼翼地放在自己的布袋子里。</p><p> 而猫却在林子里横冲直撞,把几株冰莲上的花儿塞到嘴里咀嚼。</p><p> 三月底的时候,白桦林的花儿已经先于叶子见到春天,柔软下垂的花穗伸展成瀑布。</p><p> 在林子深处,猫和莱恩碰到一头鹿。</p><p> 鹿低头饮着小河中的水,抬头看到他们,惊异地叫了一声,匆忙地叫着逃走了。</p><p> 鹿的鸣叫声就像鸟鸣一样清澈,感到惊奇的莱恩追了上去,可是鹿眨眼间便在林子里隐去了踪迹。</p><p> 猫慢慢地跟在后面,好久才赶上莱恩:“别追了,追不上的。我们本来就不属于这个林子,他见到我们自然会逃走。”</p><p> 莱恩没有说话,失落地自顾自走着。</p><p> 春天的风拂弄着白桦的花序,夕阳金色的余晖打在花穗上,一树树的花便开始缓缓地掉落。在夜色的掩盖下,骷髅花也开始凋落。</p><p> 又走了好久,一阵急促的咕咕声打破了深夜的沉寂。</p><p> 猫借着月光看向白桦林梢头,是猫头鹰。</p><p> 猫下意识地朝莱恩望去,可垂头丧气的莱恩丝毫没有意识到危险将临。</p><p> 猫头鹰一个俯冲直接飞向莱恩,将莱恩扑倒在旁边的灌木丛里。</p><p> 猫一跃跳到猫头鹰背上,喵呜着张开爪子抓着猫头鹰翅膀滚到了一旁。而莱恩吓得呆在原地。</p><p> 春寒仍旧料峭,夜晚冷冽的风刮得白桦树的花散乱地落下。</p><p> 猫头鹰咕咕叫着,猫也不甘示弱地用爪子狠狠拍打猫头鹰。</p><p> 趁猫有些力不从心,猫头鹰脱身飞到空中,落在树梢上又突然俯冲,这次,猫被扑倒在地上。</p><p> 猫竭力伸出前爪抓挠着猫头鹰的眼睛,同时后爪用力蹬着猫头鹰。</p><p> 渐渐地,猫和猫头鹰都没了力气,猫的肚皮上渗出了血,猫头鹰的面部也伤痕累累。</p><p> 最后,猫头鹰拖曳着受伤的翅膀消失在了夜幕里。</p><p> 莱恩依然被激烈的打斗场景吓得呆在原地。</p><p> 白桦树的嫩芽似乎在这短短的几分钟里匆匆地长成了嫩叶,逐渐将天空分割成一瓣又一瓣,清冽的月光默默地隐却在其中。</p><p> 林下的猫和莱恩被汹涌的黑暗吞噬,万物都被这黑暗吞噬,声音亦然。</p><p> </p><p> 当早晨的第一缕阳光透过嫩叶的间隙照向莱恩时,我们看到,莱恩仍旧呆若木鸡。</p><p> 湿漉漉的空地上,错乱地写着一行行字,那是莱恩在黑暗中摸索着用爪子划出来的。</p><p> </p><p> 无题</p><p> 明明走出了洞穴</p><p> 光还是不能为我而亮</p><p> 是有一堵墙吗?</p><p> 还是我仍在洞中?</p><p> </p><p> 难道夜晚就不配拥有晴天?</p><p> 难道撕裂的时空不能愈合?</p><p> 怕不是世界的凸凹太多</p><p> 阻隔了角角落落</p><p> </p><p> 醒来的猫舔了舔伤口,踉踉跄跄地站了起来。</p><p> 莱恩上前,却欲言又止。</p><p> 猫仿佛是笑了笑:“我只不过担心到手的食物落了空。”</p><p> 他们又向前出发了。</p><p> 一路上,到处都是散落的白桦树花穗和凋零的白色花瓣,春天温暖的阳光透过白桦林浓密的叶子,洒下支离破碎的光影。</p><p> 猫问莱恩:“你还相信自己能够走出洞穴吗?”</p><p> 莱恩没有回答,转而反问:“这有什么值得怀疑的。”</p><p> 猫又仿佛是笑了笑。</p><p> 有太多的问题,一时间不能有答案,也许到最后,也不会有答案。</p><p> 在林子里,他们又遇到了蛇,遇到了野猪,甚至遇到了猎人。</p><p> 猫和老鼠躲了又躲,猫为救老鼠受了一次又一次伤,莱恩在路上写了一首又一首诗。</p><p> 最后,在暮春的一个月夜,他们终于走出了白桦林,来到悬崖边上。</p><p> 莱恩和猫并排坐着,望着月亮,俯视悬崖下的潺潺小溪。</p><p> 夜幕下,月光流泻在白桦林上,淌在猫和莱恩的身上。莱恩的影子被清冽的光拉得很长,他的影子旁边是一个更大的影子。就像莱恩曾经梦到的一样,他看着自己身旁的影子像是张开了巨口,慢慢靠近自己的影子,最后莱恩眼睁睁地看着它突然间将自己的影子覆盖住,不同的是,当身旁的影子移开时自己的影子还在那里。</p><p> 莱恩拿出布袋子里的哲理书残页,仔仔细细地看了一遍,又叠好放回布袋子,最后将布袋子扔下了悬崖。</p><p> 猫很是吃惊,莱恩笑了笑:“这不过是个寄托罢了。”</p><p> 接着他们沉默了许久。</p><p> 暮春的风浮动着,白桦林的枝叶婆娑作响。</p><p> 莱恩突然又问起猫那个问题:“你还会吃掉我吗?”</p><p> “不知道。”</p><p> “那你之前说的那只老鼠真的被吃掉了吗?”</p><p> 猫沉思半晌。</p><p> “其实并没有,我的父亲和他一起离开了房子。” </p><p> 莱恩惊讶地问道:“就像我们一样?”</p><p> “不知道。”</p><p> 猫停顿了一会儿,又说:“我只依稀记得母亲说过,那只老鼠有着棕灰色的毛。”</p><p> 莱恩低头看了看自己,也是棕灰色的毛。</p><p> 良久,猫又说:“走了这么久,我们一直只走出了一个洞穴,现在,我们仍旧在另一个更大的洞穴深处徘徊。”</p><p> 莱恩想了很久,若有若无地点了点头。</p><p> 猫说:“洞穴是无形的,又是有形的,总是让我们感到窒息。”</p><p> “就像规则一样吗?我们看不见它,可它总是在那,决定着一切。”莱恩似懂非懂。</p><p> 猫嘲讽似的笑了笑:“可谁说必须要受制于它?要知道,不管谁说的话,都不是这个世界的准则。”</p><p> 莱恩不语。</p><p> 显而易见,世界的凸凹不平造就了洞穴的存在,而迄今为止,猫和莱恩只走出了一个洞穴,只打破了一堵墙,那就是他们之间的墙。</p><p> 莱恩拿起一个小石子,在岩石上刻下一首诗:</p><p> 凸凹</p><p> 从一个洞穴到另一个洞穴的距离</p><p> 从不该用脚步丈量</p><p> 凸凹之间的故事</p><p> 又怎是诗所能言说?</p><p> </p><p> 彼时彼刻应正如此时此刻</p><p> 此时此刻亦正如彼时彼刻</p><p> 你看啊那漫山遍野</p><p> 你可还觉得孤单?</p><p> 写完诗,莱恩突然间看到,岩石的下面,刻着一些图案,依稀可辨。</p><p> 一只猫和一只老鼠。</p><p> 莱恩喃喃:“书都是骗人的。”</p><p> 接着,在清冽的月光中,恣意流泪。</p>]]></content>
<categories>
<category> 珠玑 </category>
</categories>
<tags>
<tag> 小说 </tag>
</tags>
</entry>
<entry>
<title>操作系统原理笔记(一)</title>
<link href="/posts/2023/03/12/notes/Operating-System-Principles-1/"/>
<url>/posts/2023/03/12/notes/Operating-System-Principles-1/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Operating-System-Principles.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">操作系统原理笔记</div> <div class="tag-link-sitename"> 点击下载操作系统原理笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="一、绪论"><a href="#一、绪论" class="headerlink" title="一、绪论"></a>一、绪论</h2><h3 id="1-1-操作系统与计算机体系结构的关系"><a href="#1-1-操作系统与计算机体系结构的关系" class="headerlink" title="1.1 操作系统与计算机体系结构的关系"></a>1.1 操作系统与计算机体系结构的关系</h3><ul><li><p>操作系统的地位</p><ul><li>不解决具体问题,不负责生成解决应用问题的程序</li><li>所有硬件之上,所有软件之下,是各种软件的基础运行平台</li></ul></li><li><p>计算机系统的体系结构</p><ul><li><p>应用软件</p></li><li><p>编译器,数据库,网络等组件</p></li><li><p>操作系统</p></li><li><p>硬件</p></li></ul></li><li><p>操作系统与各层之间的关系</p><ul><li><p>操作系统对各层的管理和控制</p><ul><li>管理控制硬件:控制CPU的工作、访问存储器、设备驱动、中断处理</li><li>管理控制应用软件:提供方便简单得用户接口</li></ul></li><li><p>各层对OS的制约和影响</p><ul><li>下层硬件环境的制约:提供操作系统的运行环境、限制了操作系统的功能实现</li><li>上层用户和软件的要求:操作系统需要满足不同的应用需求</li></ul></li></ul></li><li><p>操作系统的功能</p><ul><li>进程管理<ul><li>进程控制:创建,暂停,唤醒,撤销;</li><li>进程调度:调度策略,优先级;</li><li>进程通信:进程间通信</li></ul></li><li>内存管理<ul><li>内存分配</li><li>内存共享</li><li>内存保护</li><li>虚拟内存</li></ul></li><li>设备管理<ul><li>设备的分配和调度</li><li>设备无关性</li><li>设备传输控制</li><li>设备驱动</li></ul></li><li>文件管理<ul><li>存储空间管理</li><li>文件的操作</li><li>目录的操作</li><li>文件和目录的存取权限管理</li></ul></li></ul></li></ul><h3 id="1-2-操作系统的形成与发展-★"><a href="#1-2-操作系统的形成与发展-★" class="headerlink" title="1.2 操作系统的形成与发展(★)"></a>1.2 操作系统的形成与发展(★)</h3><h4 id="1-2-1-手工操作阶段"><a href="#1-2-1-手工操作阶段" class="headerlink" title="1.2.1 手工操作阶段"></a>1.2.1 手工操作阶段</h4><ul><li>人工操作</li><li>作业<strong>独占</strong>计算机</li><li>作业<strong>串行</strong>执行</li></ul><h4 id="1-2-2-批处理阶段"><a href="#1-2-2-批处理阶段" class="headerlink" title="1.2.2 批处理阶段"></a>1.2.2 批处理阶段</h4><h5 id="联机批处理"><a href="#联机批处理" class="headerlink" title="联机批处理"></a>联机批处理</h5><ul><li>作业的I/O操作由CPU来处理:慢速I/O设备严重浪费CPU处理时间</li></ul><h5 id="脱机批处理"><a href="#脱机批处理" class="headerlink" title="脱机批处理"></a>脱机批处理</h5><ul><li>作业的I/O操作由廉价的卫星机处理</li><li>主处理器只与高速存储设备交互</li><li>DMA的原型</li></ul><h5 id="单道批处理技术"><a href="#单道批处理技术" class="headerlink" title="单道批处理技术"></a>单道批处理技术</h5><ul><li>批量:将多个程序打包形成<strong>作业队列</strong></li><li>自动:常驻监督程序(可视为操作系统)依次<strong>自动</strong>处理队列中的每个作业</li><li>单道:作业只能按照顺序依次被执行,是<strong>串行</strong>的</li><li>利用效率低:当需要执行I/O操作时,CPU空闲,而不能被其他作业使用;当需要CPU工作时,外设空闲,而不能被其他作业使用</li></ul><h5 id="多道批处理技术"><a href="#多道批处理技术" class="headerlink" title="多道批处理技术"></a>多道批处理技术</h5><ul><li><p>多道:内存中同时存放<strong>多道</strong>相互独立的程序,以<strong>脱机</strong>方式工作</p></li><li><p>提高CPU利用效率:当某道程序因某种原因(如进行I/O操作)不能继续运行下去时,操作系统便将另一道程序投入运行</p></li><li><p>宏观上并行:多个程序同时占用多个资源同时执行</p></li><li><p>微观上串行:在某个时间点,最多一个程序占用CPU;多个程序交替占用CPU</p></li><li><p>无法交互:平均周转时间长,无交互能力</p></li><li><p>辅助技术:中断、通道(DMA、达成脱机方式)</p></li></ul><h4 id="1-2-3-分时系统阶段"><a href="#1-2-3-分时系统阶段" class="headerlink" title="1.2.3 分时系统阶段"></a>1.2.3 分时系统阶段</h4><ul><li>以<strong>联机</strong>方式使用计算机</li></ul><h5 id="分时技术"><a href="#分时技术" class="headerlink" title="分时技术"></a>分时技术</h5><ul><li>把处理机时间划分成很短的时间片(如几百毫秒)轮流地分配给各个应用程序使用</li><li>如果某个程序在分配的时间片用完之前计算还未完成,该程序就暂停执行,等待下一次获得时间片后再继续计算</li></ul><h5 id="实时技术"><a href="#实时技术" class="headerlink" title="实时技术"></a>实时技术</h5><ul><li>系统能及时响应外部事件的请求,在规定的时间(deadline)范围内完成对该事件的处理,并控制实时任务协调一致运行<ul><li>强调可预测而非迅速</li><li>硬实时:若实时约束不被满足,则会导致灾难性后果</li><li>软实时:若实时约束不被满足,则会导致服务质量的降级</li></ul></li></ul><h3 id="1-3-操作系统的定义与特征"><a href="#1-3-操作系统的定义与特征" class="headerlink" title="1.3 操作系统的定义与特征"></a>1.3 操作系统的定义与特征</h3><ul><li><p>定义</p><ul><li><p>大型<strong>软件系统</strong></p></li><li><p>负责计算机系统软、硬件资源的分配</p></li><li><p>控制和协调并发活动</p></li><li><p>提供用户接口,使用户获得良好的工作环境</p></li></ul></li><li><p>特征</p><ul><li>并发<ul><li>并行是指两个或者多个事件在<strong>同一时刻</strong>发生,而并发是指两个或多个事件在<strong>同一时间间隔</strong>发生</li><li>并行是在<strong>不同实体</strong>上的多个事件,并发是在同一实体上的多个事件</li></ul></li><li>共享:系统中的资源可供多个并发执行的进程共同使用</li><li>虚拟:把一个物理上的实体变为若干个逻辑上的对应物</li><li>不确定:为多个作业的执行顺序和每个作业的执行时间是不确定的</li></ul></li></ul><h2 id="二、操作系统的结构和硬件支持"><a href="#二、操作系统的结构和硬件支持" class="headerlink" title="二、操作系统的结构和硬件支持"></a>二、操作系统的结构和硬件支持</h2><h3 id="2-1-操作系统的物质基础-★"><a href="#2-1-操作系统的物质基础-★" class="headerlink" title="2.1 操作系统的物质基础(★)"></a>2.1 操作系统的物质基础(★)</h3><h4 id="2-1-1-CPU特权级"><a href="#2-1-1-CPU特权级" class="headerlink" title="2.1.1 CPU特权级"></a>2.1.1 CPU特权级</h4><ul><li><p>设立特权级的目的:保护操作系统</p></li><li><p>CPU的两种状态</p><ul><li>管态(核态、特权态)<ul><li>操作系统的<strong>管理程序</strong>执行时机器所处的状态</li><li>可使用全部指令(<strong>包括一组特权指令</strong>)</li><li>可使用全部系统资源(<strong>包括整个存储区域</strong>)</li></ul></li><li>用户态<ul><li><strong>用户程序</strong>执行时机器所处的状态</li><li><strong>禁止使用特权指令</strong>,不能直接取用资源与改变机器状态</li><li><strong>只允许访问自己的存储区域</strong></li></ul></li></ul></li><li><p>CPU特权指令</p><ul><li>I/O指令</li><li>停机halt指令</li><li>从核态转回用户态</li><li>改变状态寄存器(MSR)的指令</li></ul></li><li><p>CPU特权级</p></li></ul><h4 id="2-1-2-中断技术"><a href="#2-1-2-中断技术" class="headerlink" title="2.1.2 中断技术"></a>2.1.2 中断技术</h4><h5 id="相关概念"><a href="#相关概念" class="headerlink" title="相关概念"></a>相关概念</h5><ul><li><p>中断的本质是受保护的状态转换——<strong>确保CPU安全地由用户态转到核态</strong>,转换的过程中不允许存在让用户程序干预的可能</p></li><li><p>CPU收到外部中断信号后,停止当前工作,转去处理该外部事件 ,处理完毕后回到原来的中断处继续运行</p></li><li><p>相关概念</p><ul><li>中断源:引起中断的事件</li><li>断点:发生中断时正在运行的程序被暂时停止,程序的暂停点称为断点。</li><li>中断是硬件和软件协同的处理的,由硬件发现中断并进入中断,进入中断后,然后让软件来执行对中断事件的处理。</li></ul></li></ul><h5 id="中断分类"><a href="#中断分类" class="headerlink" title="中断分类"></a>中断分类</h5><table><thead><tr><th align="center"></th><th align="center">IRQ</th><th align="center">Exception</th><th align="center">Syscall</th></tr></thead><tbody><tr><td align="center">产生原因</td><td align="center">CPU以外的外部设备产生的异步事件</td><td align="center">当前程序的执行所导致的同步事件</td><td align="center">当前执行的程序需调用操作系统的功能</td></tr><tr><td align="center">处理时机</td><td align="center">指令执行的间隙</td><td align="center">发生异常的指令执行过程中</td><td align="center">访管指令</td></tr><tr><td align="center">返回地址</td><td align="center">下一条指令</td><td align="center">发生异常的指令</td><td align="center">下一条指令</td></tr><tr><td align="center">举例</td><td align="center">敲击键盘、磁盘数据传输完成</td><td align="center">除零、非法内存访问</td><td align="center">ecall指令</td></tr></tbody></table><h5 id="中断响应"><a href="#中断响应" class="headerlink" title="中断响应"></a>中断响应</h5><ul><li><p>中断响应的过程</p><ul><li>识别中断源</li><li>保护断点和现场:保存断点地址、程序状态字PSW</li><li>装入中断服务程序的入口地址</li><li>进入中断服务程序</li><li>恢复现场和断点</li><li>中断返回</li></ul></li><li><p>中断响应的实质</p><ul><li>交换CPU的态</li><li>交换指令执行地址</li></ul></li></ul><h4 id="2-1-3-时钟"><a href="#2-1-3-时钟" class="headerlink" title="2.1.3 时钟"></a>2.1.3 时钟</h4><h3 id="2-2-操作系统的组织结构"><a href="#2-2-操作系统的组织结构" class="headerlink" title="2.2 操作系统的组织结构"></a>2.2 操作系统的组织结构</h3><ul><li><p>操作系统的组件</p><ul><li>核心组件:中断管理、进程管理、内存管理</li><li>外围组件:文件管理、设备管理</li></ul></li><li><p>操作系统的内核结构</p></li></ul><table><thead><tr><th align="center"></th><th align="center">单内核</th><th align="center">微内核</th><th align="center">伴生内核</th></tr></thead><tbody><tr><td align="center">特点</td><td align="center">所有组件均运行在内核态</td><td align="center">核心组件运行在内核态,外围组件运行在用户态</td><td align="center">同一台机器运行多个OS,包括主OS和伴生OS</td></tr><tr><td align="center">优点</td><td align="center">结构简单,执行效率高</td><td align="center">内核小,稳定</td><td align="center">伴生OS结构简单</td></tr><tr><td align="center">缺点</td><td align="center">内核庞大,难以维护</td><td align="center">采用IPC通讯,效率低</td><td align="center">完整系统结构复杂</td></tr><tr><td align="center">例子</td><td align="center">UNIX、Linux</td><td align="center">Windows</td><td align="center">PKE、Docker容器</td></tr></tbody></table><ul><li><p>IPC通讯:内核把用户请求服务的消息传给服务进程;服务进程接受并执行用户服务请求;内核用消息把结果返回给用户</p><img src="https://picbed.cloudchewie.com/blog/post/Operating-System-Principles-1/Chp1-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8%E7%BB%93%E6%9E%84%E5%AF%B9%E6%AF%94.png!blogimg" class="" width="400" title="操作系统内核结构对比"></li></ul><h3 id="2-3-程序的链接-★"><a href="#2-3-程序的链接-★" class="headerlink" title="2.3 程序的链接(★)"></a>2.3 程序的链接(★)</h3><h3 id="2-4-用户接口-★"><a href="#2-4-用户接口-★" class="headerlink" title="2.4 用户接口(★)"></a>2.4 用户接口(★)</h3><ul><li><p>批处理操作系统:作业控制语言</p></li><li><p>分时操作系统、个人计算机操作系统:作业控制语言、键盘命令、图形用户界面</p></li><li><p>操作系统如何提供服务(<strong>系统调用</strong>):</p><ul><li>操作系统提供实现各种功能的例行子程序,每个功能对应一个功能号;</li><li>CPU提供<strong>访管指令</strong>:svc n,其中svc表示访管指令的记忆符,n为功能号;</li><li>应用程序通过访管指令调用操作系统例程;</li><li>处理机执行到访管指令时发生中断,该中断称为访管中断。</li></ul></li><li><p>系统调用与一般库函数的区别</p><ul><li>系统调用代码驻留在内存中,属于操作系统,其执行回引起CPU状态由用户态转到核态,如getchar()函数</li><li>一般库函数由开发软件提供,不会引起CPU状态的变化,如max()函数</li></ul></li><li><p>系统调用的实现</p><ul><li>每个系统调用对应一个系统调用号;</li><li>每个系统调用对应一个执行程序段;</li><li>每个系统调用要求一定数量的输入参数和返回值</li></ul></li></ul><table><thead><tr><th align="center">UNIX/Linux</th><th align="center">Win32</th><th align="center">Usage</th></tr></thead><tbody><tr><td align="center">fork</td><td align="center">CreatProcess</td><td align="center">创建进程</td></tr><tr><td align="center">waitpid</td><td align="center">WaitForSingleObject</td><td align="center">等待进程终止</td></tr><tr><td align="center">open/close</td><td align="center">CreatFile/CloseHandle</td><td align="center">创建或打开/关闭文件</td></tr><tr><td align="center">read/write</td><td align="center">ReadFile/WriteFile</td><td align="center">读/写文件</td></tr><tr><td align="center">lseek</td><td align="center">SetFilePointer</td><td align="center">移动文件指针</td></tr><tr><td align="center">mkdir/rmdir</td><td align="center">Creat/Remove Directory</td><td align="center">建立/删除目录</td></tr><tr><td align="center">stat</td><td align="center">GetFileAttributesEx</td><td align="center">获得文件属性</td></tr></tbody></table><ul><li>Linux的内核陷入指令为int 80h中断指令</li></ul><h2 id="三、进程管理"><a href="#三、进程管理" class="headerlink" title="三、进程管理"></a>三、进程管理</h2><h3 id="3-1-进程引入"><a href="#3-1-进程引入" class="headerlink" title="3.1 进程引入"></a>3.1 进程引入</h3><ul><li><p>顺序程序(单道系统)</p><ul><li><p>程序的一次执行过程称为一次计算,它由许多简单的操作组成</p></li><li><p>一个计算的若干操作必须按照严格的先后次序顺序地执行,这个计算过程就是程序的顺序执行过程</p></li><li><p>特点</p><ul><li><p>顺序性:处理机的操作严格<strong>按照程序所规定的顺序依次执行</strong></p></li><li><p>封闭性:程序一旦开始执行,就<strong>不会受到外界因素的影响</strong></p></li><li><p>可再现性:程序执行的结果与它的执行速度无关 (即与时间无关),而只与初始条件有关;初始条件相同,程序执行的结果一定相同</p></li></ul></li></ul></li><li><p>并发程序</p><ul><li><p>若干个程序段同时在系统中运行,这些程序段的执行<strong>在时间上重叠</strong></p></li><li><p>一个程序段的执行尚未结束,另一个程序段的执行已经开始,称这几个程序段是<strong>并发执行</strong>的</p></li><li><p>并发语句记号</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">cobegin</span><br><span class="line">S1,S2,S3,...,Sn</span><br><span class="line">coend</span><br></pre></td></tr></table></figure></li><li><p>特点</p><ul><li><p>失去封闭性和可再现性:两个并发程序若干操作的先后顺序</p><ul><li>程序并发执行时的结果与各并发程序的相对速度有关</li><li>即给定相同的初始条件,若不加以控制,也可能得到不同的结果,此为与时间有关的错误</li></ul></li><li><p>一个程序可以对应多个计算</p></li><li><p>多个计算之间会有并发执行的相互制约</p><ul><li>直接制约:共享变量</li><li>间接制约:资源共享</li></ul></li></ul></li></ul></li></ul><h3 id="3-2-进程概念"><a href="#3-2-进程概念" class="headerlink" title="3.2 进程概念"></a>3.2 进程概念</h3><h4 id="3-2-1-进程定义"><a href="#3-2-1-进程定义" class="headerlink" title="3.2.1 进程定义"></a>3.2.1 进程定义</h4><ul><li><p>指一个具有一定独立功能的程序关于某个数据集合的一次运行活动,即程序的一次执行</p></li><li><p>进程与程序的区别</p><ul><li><p>程序是静态的(存储在内存/外存中的代码),进程是动态的(程序在处理机运行的过程,是运行中的程序);</p></li><li><p>进程是一个独立运行的活动单位,是竞争系统资源的基本单位;</p></li><li><p>一个程序可以对应多个进程,一个进程至少包含一个程序</p></li></ul></li></ul><h4 id="3-2-2-进程状态-★"><a href="#3-2-2-进程状态-★" class="headerlink" title="3.2.2 进程状态(★)"></a>3.2.2 进程状态(★)</h4><ul><li><p>三种基本状态</p><ul><li>运行态:该进程已获得运行所必需的资源,它的程序<strong>正在处理机上执行</strong>;</li><li>等待态:进程正等<strong>待着某一事件的发生而暂时停止执行</strong>。这时,即使给它CPU控制权,它也<strong>无法执行</strong>;</li><li>就绪态:进程已获得除CPU之外的运行所必需的资源,<strong>一旦得到CPU控制权,立即可以运行</strong></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Operating-System-Principles-1/Chp1-%E8%BF%9B%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81%E5%9B%BE.png!blogimg" class="" width="400" title="进程状态变迁图"></li><li><p>进程的变迁原因</p><ul><li>就绪->执行:进程调度即可就绪状态转为运行状态</li><li>等待->就绪:处于等待状态的进程中相关服务完成或者相关资源获得完成</li><li>运行->等待:进程提出某种服务请求,比如说I/O</li><li>运行->就绪:<strong>在分时系统中,时间片到</strong>,才会发生这种变迁</li></ul></li><li><p>进程基本状态的拓展</p><ul><li>拓展1:程序执行完了,进程还可以被回收;</li><li>拓展2:添加挂起操作,添加静止状态,静止表示当前进程不在主存里面,在虚存里面;</li><li>Unix进程,运行态分成用户态运行和核心态运行</li></ul></li></ul><h4 id="3-2-3-进程描述"><a href="#3-2-3-进程描述" class="headerlink" title="3.2.3 进程描述"></a>3.2.3 进程描述</h4><ul><li>进程的组成<ul><li>进程内部的程序和数据:描述进程本身所应完成的功能</li><li>进程控制块PCB:描述进程与其他进程、系统资源的关系,以及进程所处的状态</li></ul></li><li>进程控制块的内容<ul><li>进程标志符:进程符号名或ID</li><li>进程当前状态</li><li>进程队列的指针next:处于同一状态的下一个进程的PCB地址</li><li>进程优先级</li><li>CPU现场保护区</li><li>通信信息、家族联系、占有资源清单</li></ul></li><li>Linux中的PCB结构称为task_struct,所有进程均以task_struct链表的形式存储在内存中</li><li>PCB队列的组织<ul><li>就绪队列:所有处于就绪状态的队列</li><li>等待队列:有多个等待队列,每个队列表示所有因为<strong>同个某种原因</strong>而等待的进程</li><li>运行指针:当前是什么进程正在运行</li></ul></li></ul><h3 id="3-3-进程控制原语-★"><a href="#3-3-进程控制原语-★" class="headerlink" title="3.3 进程控制原语(★)"></a>3.3 进程控制原语(★)</h3><h4 id="3-3-1-进程创建"><a href="#3-3-1-进程创建" class="headerlink" title="3.3.1 进程创建"></a>3.3.1 进程创建</h4><ul><li><p>进程创建原语:create(name,priority)</p><ul><li>其中name为标识符,priority是优先级</li><li>创建一个具有指定标识符的进程,建立进程的PCB结构</li></ul></li><li><p>实现方法</p><ul><li>查找PCB池,是否出现同名现象</li><li>向系统申请一个空闲PCB,没有空闲PCB则出错退出</li><li>将入口信息填入PCB</li><li>PCB入就绪队列</li><li>返回进程PID</li></ul></li><li><p>进程创建:Linux中的fork()</p><ul><li>创建一个子进程,它从父进程继承整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。</li><li>创建过程<ul><li>为新进程分配一个新的PCB结构;</li><li>为子进程赋一个唯一的进程标识号 (PID);</li><li>为子进程复制一个父进程上下文的逻辑副本——父子进程将执行完全相同的代码;</li><li>增加与该进程相关联的文件表和索引节点表的引用数——父进程打开的文<br>件子进程可以继续使用;</li><li>对父进程返回子进程的进程号,对子进程返回零</li></ul></li></ul></li><li><p>进程更换:Linux中的exec()</p><ul><li>更换进程执行代码,<strong>更换正文段,数据段</strong></li><li>格式:exec(文件名,参数表,环境变量表)</li><li>例如:execlp(“max”,15,18,10,0);execvp(“max”,argp)</li></ul></li></ul><h4 id="3-3-2-进程撤销"><a href="#3-3-2-进程撤销" class="headerlink" title="3.3.2 进程撤销"></a>3.3.2 进程撤销</h4><ul><li><p>进程撤销原语:kill(),exit()</p><ul><li>撤消当前运行的进程并转进程调度程序</li></ul></li><li><p>实现方法</p><ul><li>由运行指针获得当前进程pid</li><li>释放本进程占用的资源给父进程</li><li>从总链队列中删除该进程</li><li>释放PCB结构</li><li>转进程调度</li></ul></li><li><p>进程撤销:Linux中的exit()</p><ul><li>撤销一个进程,它停止当前进程的运行,清除其使用的内存空间, 销毁其在内核中的各种数据结构;</li><li>进程状态变为zombie僵尸态:<strong>仍保留其PCB结构,等待父进程回收</strong></li><li>若其父进程正在等待该进程的终止,则父进程可立即得到其返回的整数status</li><li>僵尸进程:若子进程调用exit(),而父进程并没有调用wait()或waitpid()获取子进程的状态信息,那么子进程的PCB仍然保存在系统中,此时该子进程称为僵尸进程;</li><li>孤儿进程:当一个父进程由于正常完成工作而退出或由于其他情况被终止,它的一个或多个子进程却还在运行,这些子进程将成为孤儿进程;</li><li>孤儿进程将被1号进程接管,且1号进程定期清除僵尸进程</li></ul></li></ul><h4 id="3-3-3-进程等待"><a href="#3-3-3-进程等待" class="headerlink" title="3.3.3 进程等待"></a>3.3.3 进程等待</h4><ul><li><p>进程等待原语:susp(chan)</p><ul><li>其中chan为进程等待的原因</li><li>中止调用susp的进程的执行,并将其加入到等待chan的等待队列中,转进程调度</li></ul></li><li><p>实现方法</p><ul><li>保护当前进程的CPU现场到其PCB结构中</li><li>设置该进程为<strong>等待态</strong></li><li>将该进程的PCB结构插入到chan对应的等待队列中</li><li>转进程调度</li></ul></li><li><p>进程等待:Linux中的wait()</p><ul><li>父进程通过调用wait(int* status)函数使其暂停执行,直到它的一个子进程结束为止;</li><li>其返回值是终止运行的子进程的PID;</li><li>参数status所指向的变量存放子进程的退出码,即从子进程的main函数返回的值或子进程中exit()函数的参数</li></ul></li><li><p>进程等待:Linux中的waitpid()</p><ul><li>父进程通过调用wait(pid_t pid, int * status, int options)函数使其暂停执行,直到特定子进程结束为止</li></ul></li></ul><h4 id="3-3-4-进程唤醒"><a href="#3-3-4-进程唤醒" class="headerlink" title="3.3.4 进程唤醒"></a>3.3.4 进程唤醒</h4><ul><li>wakeup(chan)<ul><li>其中chan为被唤醒进程等待的原因</li><li>当进程等待的事件发生时,由发现者进程唤醒等待该事件的进程</li></ul></li><li>实现方法<ul><li>找到chan对应的等待队列</li><li>将该队列的首个进程移出等待队列</li><li>设置该进程为<strong>就绪态</strong></li><li>将该进程插入到就绪队列</li><li>进程调度</li></ul></li></ul><h3 id="3-4-进程的相互制约关系"><a href="#3-4-进程的相互制约关系" class="headerlink" title="3.4 进程的相互制约关系"></a>3.4 进程的相互制约关系</h3><ul><li><p>临界资源:一次只允许一个进程使用的资源</p><ul><li>硬件:输入机,打印机,磁带机</li><li>软件:公共变量,队列</li></ul><blockquote><p>当两个进程公用一个变量时,它们必须顺序地使用,一个进程对公用变量操作完成后,另一个进程才能访问修改该变量</p></blockquote></li><li><p>临界区:对于公共变量或公共存储区进行访问和修改的程序段</p></li><li><p>临界区访问规则</p><ul><li>空闲则入:没有进程在临界区时,任何进程都可以进入临界区;</li><li>忙则等待:有进程在临界区时,其他进程均不能进入临界区;</li><li>有限等待:等待进入临界区的进程不能无限等待;</li><li>让权等待:不能进入临界区的进程应释放CPU</li></ul></li><li><p>进程互斥</p><ul><li><p>当某一进程正在访问某一存储区时,不允许其他进程访问或者修改该存储区的内容,这种制约关系称为进程互斥</p></li><li><p><strong>同一临界资源</strong>的临界区才需要互斥进入</p></li></ul></li><li><p>进程同步</p><ul><li><p><strong>并发</strong>进程在一些关键点上可能需要<strong>互相等待与互通消息</strong>, 这种制约关系称为进程同步</p></li><li><p>在病人看病过程中,<u>看病活动</u>需要等待<u>化验活动</u>作出的化验结果,而<u>化验活动</u>需要等待来自<u>看病活动</u>的化验请求</p></li></ul></li><li><p>同步反映的是合作关系;互斥反映的是竞争关系</p></li></ul><h3 id="3-5-进程同步机构-★"><a href="#3-5-进程同步机构-★" class="headerlink" title="3.5 进程同步机构(★)"></a>3.5 进程同步机构(★)</h3><h4 id="3-5-1-锁"><a href="#3-5-1-锁" class="headerlink" title="3.5.1 锁"></a>3.5.1 锁</h4><ul><li>用一个变量w代表某种资源的状态:w=1表示资源被占用,否则表示资源没有被占用</li><li>上锁原语:lock(w)<ul><li>执行到lock的时候判断w是多少</li><li>如果w=1,就被阻塞,进程无法往下继续运行,直到什么时候w=0</li><li>如果w=0,不会被阻塞,进程可以继续执行,并且会把w赋值为1</li></ul></li><li>开锁原语:unlock(w)<ul><li>执行到unlock的时候,把w赋值为0即可</li></ul></li></ul><h4 id="3-5-2-信号灯"><a href="#3-5-2-信号灯" class="headerlink" title="3.5.2 信号灯"></a>3.5.2 信号灯</h4><ul><li><p>信号灯是一个确定的二元组(s,q),其中s是一个具有非负初值的整型变量,q是一个初始状态为空的队列。</p><ul><li>变量值s≥0时,表示绿灯,进程执行;</li><li>变量值s<0 时,表示红灯,进程停止执行;</li><li>创建信号灯时应说明信号灯的意义和s的初值,且初值绝不能为负值</li></ul></li><li><p>P操作</p><ul><li>把信号灯变量s的值<strong>减一</strong></li><li>操作后,如果s为负,调用P操作的进程阻塞,并插入到信号灯变量s对应的等待队列中,否则继续运行</li></ul></li><li><p>V操作</p><ul><li>把信号灯变量s的值<strong>加一</strong></li><li>操作后,如果s非正,则从信号灯变量s对应的等待队列中取出一个进程放入就绪队列,否则继续运行</li></ul></li></ul><h4 id="3-5-3-进程互斥的实现"><a href="#3-5-3-进程互斥的实现" class="headerlink" title="3.5.3 进程互斥的实现"></a>3.5.3 进程互斥的实现</h4><ul><li><p>对于每一个锁,进入临界区之前上锁,离开临界区的时候解锁</p></li><li><p>每个临界区都有有一个临界信号灯管理,进入临界区就P(s),离开临界区就V(s)</p></li><li><p>基本原则</p><ul><li><p>信号灯的初值为非负整数</p></li><li><p>除初始化外,只能使用P、V原语对信号灯进行操作</p></li><li><p>P、V操作一定成对出现</p><blockquote><p>遗漏P操作则不能保证互斥访问,遗漏V操作则不能在使用临界资源之后将其释放(给其他等待的进程)</p></blockquote></li></ul></li></ul><h4 id="3-5-4-进程同步问题"><a href="#3-5-4-进程同步问题" class="headerlink" title="3.5.4 进程同步问题"></a>3.5.4 进程同步问题</h4><ul><li>进程流图<ul><li>表示进程之间执行的先后次序,某些进程的完成代表某些进程可以开始执行的顺序</li><li>可以用多个信号灯表示,在每个进程前面加上若干个P操作,在每个进程后面加上若干个V操作</li><li>其中V操作用于通知其他进程本进程已经执行完毕(相当于消息发送者)</li><li>P操作用于接受上一层V操作发送来的消息</li><li>假如说有一个关系p1->p2,p1执行完了才能执行p2,那么就有一个信号灯,初值为0,在p2的开始加上P操作,p1的末尾加上V操作,有多少对这样的关系就有多少个信号灯</li></ul></li><li>共享缓冲区<ul><li>问题概述:一个读进程,一个写进程,一个缓冲区</li><li>转化成进程流图:只有读完了,才能写,只能写完了,才能读</li><li>信号灯<ul><li>sb代表缓冲区的空位置数,初值为1</li><li>sa代表缓冲区的数据数,初值为0</li></ul></li><li>写进程<ul><li>p(sb)</li><li>数据放入buf</li><li>v(sa)</li></ul></li><li>读进程<ul><li>p(sa)</li><li>取数据</li><li>v(sb)</li></ul></li></ul></li><li>生产者消费者<ul><li>问题概述<ul><li><strong>若干个生产者</strong>和若干个消费者共享一个<strong>可同时容纳多个产品的缓冲区</strong></li><li>生产者不断生产产品放入缓冲区,消费者不断从缓冲区取出产品并消耗</li><li>任何时刻最多只能有一个生产者或消费者访问缓冲区</li><li>禁止生产者向满缓冲区放入产品</li><li>禁止消费者从空缓冲区取出产品</li></ul></li><li>缓冲区有界<ul><li>同步问题分析<ul><li><strong>互斥</strong>访问缓冲区</li><li>由于缓冲区无界,生产者可以一直生产产品</li><li>消费者需要根据<strong>满缓冲区的数量</strong>决定是否消费</li></ul></li><li>信号灯<ul><li>同步信号灯nfull:表示满缓冲区的数量,初值为0</li><li>互斥信号灯mutex:表示缓冲区是否被占用,初值为1</li></ul></li></ul></li><li>缓冲区无界<ul><li>同步问题分析<ul><li><strong>互斥</strong>访问缓冲区</li><li>生产者需要根据<strong>空缓冲区的数量</strong>决定是否生产</li><li>消费者需要根据<strong>满缓冲区的数量</strong>决定是否消费</li></ul></li><li>信号灯<ul><li>同步信号灯nfull:表示满缓冲区的数量,初值为0</li><li>同步信号灯nempty:表示空缓冲区的数量,初值为n</li><li>互斥信号灯mutex:表示缓冲区是否被占用,初值为1</li></ul></li></ul></li></ul></li></ul><h3 id="3-6-线程"><a href="#3-6-线程" class="headerlink" title="3.6 线程"></a>3.6 线程</h3><ul><li><p>进程模型</p><ul><li><p>进程是资源占用的基本单位:进程拥有主存、设备、文件等系统资源的使用权</p></li><li><p>进程是调度执行的基本单位:操作系统以进程为单位进行处理机的调度</p></li><li><p>不足:进程创建、切换、通信开销大</p></li></ul></li><li><p>多线程模型</p><ul><li>在进程内增加一类实体——<strong>线程作为调度的基本单位</strong></li><li><strong>同一进程内的线程共享相同的地址空间</strong></li><li>对于共享的数据,线程使用的同步机制与进程一样</li><li>不足:一个线程崩溃,会导致其所属进程内的所有线程崩溃</li></ul></li><li><p>多核处理器</p><ul><li>设$f$为程序中能够并行的部分的运行时间在整个程序运行时间中的占比</li><li>加速比=在单处理器上执行程序的时间/在N个处理器上执行程序的时间=$\frac{1}{(1-f)+\frac{f}{N}}$</li></ul></li><li><p>两种线程模型</p><img src="https://picbed.cloudchewie.com/blog/post/Operating-System-Principles-1/Chp1-%E4%B8%A4%E7%A7%8D%E7%BA%BF%E7%A8%8B%E5%AF%B9%E6%AF%94%E5%9B%BE.png!blogimg" class="" width="400" title="两种线程对比图"></li><li><p>Linux中的线程</p><ul><li>线程创建:pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg)<ul><li>其中thread为指向创建的线程标识符的指针;attr设置线程属性;start_routine为线程运行函数地址;arg为运行函数的参数</li></ul></li><li>线程等待: pthread_join(pthread_t thread,void **thread_return)<ul><li>其中thread为被等待的线程标识符;thread_return为一个用户定义的指针,用来存储被等待线程的返回值</li><li>调用该函数的线程将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回</li></ul></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 操作系统 </tag>
</tags>
</entry>
<entry>
<title>操作系统原理笔记(二)</title>
<link href="/posts/2023/03/12/notes/Operating-System-Principles-2/"/>
<url>/posts/2023/03/12/notes/Operating-System-Principles-2/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Operating-System-Principles.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">操作系统原理笔记</div> <div class="tag-link-sitename"> 点击下载操作系统原理笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="四、资源管理"><a href="#四、资源管理" class="headerlink" title="四、资源管理"></a>四、资源管理</h2><h3 id="4-1-资源管理概述"><a href="#4-1-资源管理概述" class="headerlink" title="4.1 资源管理概述"></a>4.1 资源管理概述</h3><ul><li><p>资源管理的目标</p><ul><li>高利用率:保证资源的<strong>高利用率</strong></li><li>避免饥饿:在“合理”时间内使所有顾客有获得所需资源的机会</li><li>互斥使用:对不可共享的资源实施<strong>互斥</strong>使用</li><li>避免死锁:防止由资源分配不当而引起的<strong>死锁</strong></li></ul></li><li><p>资源管理的功能</p><ul><li>描述<strong>资源数据结构</strong>:包含资源的物理名、逻辑名、类型、地址、分配状态等</li><li>确定<strong>资源分配原则</strong>:决定资源应分给谁,何时分配,分配多少等问题</li><li><strong>实施资源分配</strong>:执行资源分配、资源收回工作</li><li>存取控制和安全保护:对资源的存取进行控制并对资源实施安全保护措施</li></ul></li><li><p>资源分配的类型</p><ul><li><p>静态分配(基于作业调度):<strong>在进程运行前</strong>,操作系统即分配该进程所需的全部资源</p></li><li><p>动态分配(基于进程调度):<strong>在进程运行中</strong>,边运行边向操作系统提出资源申请,操作系统根据申请分配资源</p></li></ul></li></ul><h3 id="4-2-资源分配机构"><a href="#4-2-资源分配机构" class="headerlink" title="4.2 资源分配机构"></a>4.2 资源分配机构</h3><ul><li>资源描述器:描述各类资源的<strong>最小分配单位</strong>的数据结构<ul><li>包含资源名,类型,大小,地址,分配标志,描述器连接信息,存取权限等</li></ul></li><li>资源信息块:描述某类资源的请求者、可用资源和资源分配程序等必要信息的数据结构</li></ul><table><thead><tr><th align="center">内容</th><th align="center">指向</th></tr></thead><tbody><tr><td align="center">等待队列头指针</td><td align="center">请求者队列</td></tr><tr><td align="center">可利用资源队列头指针</td><td align="center">可利用资源队列</td></tr><tr><td align="center">资源分配程序入口地址</td><td align="center">资源分配程序</td></tr></tbody></table><ul><li><p>中央处理机资源信息块</p><img src="https://picbed.cloudchewie.com/blog/post/Operating-System-Principles-2/Chp1-%E4%B8%AD%E5%A4%AE%E5%A4%84%E7%90%86%E6%9C%BA%E8%B5%84%E6%BA%90%E4%BF%A1%E6%81%AF%E5%9D%97.png!blogimg" class="" width="400" title="中央处理机资源信息块"></li></ul><h3 id="4-3-资源分配策略-★"><a href="#4-3-资源分配策略-★" class="headerlink" title="4.3 资源分配策略(★)"></a>4.3 资源分配策略(★)</h3><ul><li>先请求先服务<ul><li>队列结构:<strong>按请求的先后次序排序</strong>,每一个新产生的请求均排在队尾;</li><li>当资源可用时,取队首元素,并满足其需要</li></ul></li><li>优先调度<ul><li>队列结构:<strong>按优先级的高低排序</strong>,对每个进程指定优先级,每一个新产生的请求,按其优先级的高低插到相应的位置;</li><li>当资源可用时,取队首元素,并满足其需要</li></ul></li></ul><h3 id="4-4-死锁-★"><a href="#4-4-死锁-★" class="headerlink" title="4.4 死锁(★)"></a>4.4 死锁(★)</h3><h4 id="4-4-1-死锁的定义"><a href="#4-4-1-死锁的定义" class="headerlink" title="4.4.1 死锁的定义"></a>4.4.1 死锁的定义</h4><ul><li><p>定义</p><ul><li><p>在两个或多个并发进程中,如果<strong>每个</strong>进程持有某种资源而又都等待着别的进程释放它或它们正占有着的资源,否则就不能向前推进。此时,称这一组进程产生了死锁。</p></li><li><p><strong>进程在占有某个资源而请求某种资源,当该进程占有的资源是别人请求的资源时,就可能产生死锁</strong></p></li></ul></li><li><p>进程-资源分配图</p><ul><li>两类顶点<ul><li><p>所有的进程P</p></li><li><p>所有的资源R</p></li><li><p>两类有向边</p><ul><li>资源请求边:如果进程$P_i$请求资源$R_j$,则存在一条由$P_i$指向$R_j$的有向边</li><li>资源分配边:如果资源$R_j$分配给进程$P_i$,则存在一条由$R_j$指向$P_i$的有向边</li></ul></li></ul></li></ul></li></ul><h4 id="4-4-2-产生死锁的必要条件"><a href="#4-4-2-产生死锁的必要条件" class="headerlink" title="4.4.2 产生死锁的必要条件"></a>4.4.2 产生死锁的必要条件</h4><ul><li><p>互斥条件:涉及的资源是非共享的,为<strong>临界资源</strong></p></li><li><p>不剥夺条件(非抢占):进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走</p></li><li><p>部分分配:进程每次只申请他所需要的资源的一部分,在等待新一批资源的时候,进程<strong>继续占用</strong>分配到的资源</p></li><li><p>环路条件:<strong>存在一种进程的循环链</strong>,链中的每一个进程已获得的资源同时被链中下一个进程所请求</p></li></ul><h4 id="4-4-3-解决死锁"><a href="#4-4-3-解决死锁" class="headerlink" title="4.4.3 解决死锁"></a>4.4.3 解决死锁</h4><ul><li><p>死锁预防</p><ul><li>静态预防死锁:在作业调度的时候就给选中的作业<strong>分配它所需要的全部资源</strong>(破坏部分分配)</li><li>有序资源分配法:系统中所有资源都给定一个唯一的编号,所有分配请求必须以上升的次序进行。当遵守上升次序的规则时,若资源可用,则予以分配(破坏环路条件)</li></ul></li><li><p>死锁避免(银行家算法)</p><ul><li><p>要求进程声明需要资源的最大数目,在分配资源时判断是否会出现死锁,只有在不会出现死锁时才分配资源</p></li><li><p>数据结构</p><img src="https://picbed.cloudchewie.com/blog/post/Operating-System-Principles-2/Chp1-%E9%93%B6%E8%A1%8C%E5%AE%B6%E7%AE%97%E6%B3%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="银行家算法数据结构"></li><li><p>算法过程</p><ul><li>寻找一个没有标记的进程$p_i$,该进程需满足资源请求矩阵$D$的第$i$行向量小于或等于剩余资源向量$R$(这意味着能够满足该进程的资源请求);</li><li>如果存在这样的进程,则将资源分配矩阵$A$的第$i$行向量加到$R$中(这意味着该进程执行后 ,将把已占有资源释放给剩余资源池),标记该进程,并转到第1步;如果找不到这样的进程,则转到第3步;</li><li>如果所有进程均被标记,则系统处于安全状态(即所有进程均能被执行);否则系统处于不安全状态</li></ul></li></ul></li><li><p>死锁的检测和解除</p><ul><li>对资源的分配不加任何限制,也不采取死锁避免措施,而是系统定时地运行一个“死锁检测”程序,检测到死锁后采取措施解除</li><li>根据进程-资源分配图检测死锁<ul><li>如果进程-资源分配图中无环路,则此时系统没有发生死锁;</li><li>如果进程-资源分配图中有环路,且每个资源类中<strong>仅有一个资源</strong>,则系统中发生了死锁,此时,环路是系统发生死锁的<strong>充要条件</strong>,环路中的进程便为死锁进程;</li><li>如果进程-资源分配图中有环路,且涉及的资源类中<strong>有多个资源</strong>,则环路的存在只是产生死锁的<strong>必要条件</strong>而不是充分条件</li></ul></li><li>解除死锁<ul><li>立即结束所有进程的执行,并重启操作系统</li><li>撤销陷于死锁的所有进程</li><li>逐个撤销陷于死锁的进程,回收其资源,直至死锁解除</li><li>剥夺陷于死锁的进程占用的资源,但并不撤销它, 直至死锁解除</li><li>根据系统保存的检查点,让所有进程回退,直到足以解除死锁</li></ul></li></ul></li><li><p>死锁定理:P个进程共享m个同类资源,如果所有进程对资源的最大需要数目之和小于P+m,该系统不会发生死锁</p></li></ul><h2 id="五、处理机调度"><a href="#五、处理机调度" class="headerlink" title="五、处理机调度"></a>五、处理机调度</h2><h3 id="5-1-作业调度"><a href="#5-1-作业调度" class="headerlink" title="5.1 作业调度"></a>5.1 作业调度</h3><h4 id="5-1-1-作业调度概述"><a href="#5-1-1-作业调度概述" class="headerlink" title="5.1.1 作业调度概述"></a>5.1.1 作业调度概述</h4><ul><li><p>对存放在辅存设备上的大量作业,以一定的策略进行挑选,分配主存等必要的资源,建立作业对应的进程,使其投入运行</p><ul><li>作业存储在辅存设备上</li><li>在辅存中挑选一个作业,将其载入到主存</li><li>作业被载入到主存后,建立其对应的进程结构,使之投入运行</li><li>运行完毕,回到第二步</li></ul></li><li><p>作业控制块JCB:存放作业控制和管理信息</p></li><li><p>作业调度的因素</p><ul><li>注意系统资源均衡使用</li><li>保证提交的作业在截止时间内完成</li><li>设法缩短作业平均周转时间</li></ul></li></ul><h4 id="5-1-2-作业调度算法的性能衡量"><a href="#5-1-2-作业调度算法的性能衡量" class="headerlink" title="5.1.2 作业调度算法的性能衡量"></a>5.1.2 作业调度算法的性能衡量</h4><ul><li>周转时间:作业<strong>提交</strong>给计算机系统到该作业的结果<strong>返回</strong>给用户所需要的时间</li><li>平均周转时间:$t=\frac{1}{n}\sum_\limits{i=1}^{n}t_i$</li><li>带权周转时间:一个作业的周转时间与其运行时间的比值$w_i=\frac{t_i}{t_{ri}}$</li><li>平均带权周转时间:$w=\frac{1}{n}\sum_\limits{i=1}^{n}w_i$</li></ul><h4 id="5-1-3-作业调度算法-★"><a href="#5-1-3-作业调度算法-★" class="headerlink" title="5.1.3 作业调度算法(★)"></a>5.1.3 作业调度算法(★)</h4><ul><li>先来先服务:优先考虑<strong>等待时间最长</strong>的作业</li></ul><table><thead><tr><th align="center">作业</th><th align="center">提交时间$T_{si}$</th><th align="center">运行时长$T_{ri}$</th><th align="center">开始时间</th><th align="center">结束时间$T_{ci}$</th><th align="center">周转时长$T_i$</th><th align="center">带权周转时间$w_i$</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">8.00</td><td align="center">2.00</td><td align="center">8.00</td><td align="center">10.00</td><td align="center">2.00</td><td align="center">1</td></tr><tr><td align="center">2</td><td align="center">8.50</td><td align="center">0.50</td><td align="center">10.00</td><td align="center">10.50</td><td align="center">2.00</td><td align="center">4</td></tr><tr><td align="center">3</td><td align="center">9.00</td><td align="center">0.10</td><td align="center">10.50</td><td align="center">10.60</td><td align="center">1.60</td><td align="center">16</td></tr><tr><td align="center">4</td><td align="center">9.50</td><td align="center">0.20</td><td align="center">10.60</td><td align="center">10.80</td><td align="center">1.30</td><td align="center">6.5</td></tr></tbody></table><p>平均周转时间$t=1.725$,平均带权周转时间$w=6.875$</p><ul><li>短作业优先调度:优先考虑<strong>运行时间最短</strong>的作业</li></ul><table><thead><tr><th align="center">作业</th><th align="center">提交时间$T_{si}$</th><th align="center">运行时长$T_{ri}$</th><th align="center">开始时间</th><th align="center">结束时间$T_{ci}$</th><th align="center">周转时长$T_i$</th><th align="center">带权周转时间$w_i$</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">8.00</td><td align="center">2.00</td><td align="center">8.00</td><td align="center">10.00</td><td align="center">2.00</td><td align="center">1</td></tr><tr><td align="center">2</td><td align="center">8.50</td><td align="center">0.50</td><td align="center">10.30</td><td align="center">10.80</td><td align="center">2.30</td><td align="center">4.6</td></tr><tr><td align="center">3</td><td align="center">9.00</td><td align="center">0.10</td><td align="center">10.00</td><td align="center">10.10</td><td align="center">1.10</td><td align="center">11</td></tr><tr><td align="center">4</td><td align="center">9.50</td><td align="center">0.20</td><td align="center">10.10</td><td align="center">10.30</td><td align="center">0.80</td><td align="center">4</td></tr></tbody></table><p>平均周转时间$t=1.55$,平均带权周转时间$w=5.15$</p><ul><li>响应比高者优先调度:<strong>响应比=响应时间/运行时间</strong>,其中响应时间表示运行时间与已等待时间之和</li></ul><table><thead><tr><th align="center">作业</th><th align="center">提交时间$T_{si}$</th><th align="center">运行时长$T_{ri}$</th><th align="center">开始时间</th><th align="center">结束时间$T_{ci}$</th><th align="center">周转时长$T_i$</th><th align="center">带权周转时间$w_i$</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">8.00</td><td align="center">2.00</td><td align="center">8.00</td><td align="center">10.00</td><td align="center">2.00</td><td align="center">1</td></tr><tr><td align="center">2</td><td align="center">8.50</td><td align="center">0.50</td><td align="center">10.10</td><td align="center">10.60</td><td align="center">2.10</td><td align="center">4.2</td></tr><tr><td align="center">3</td><td align="center">9.00</td><td align="center">0.10</td><td align="center">10.00</td><td align="center">10.10</td><td align="center">1.10</td><td align="center">11</td></tr><tr><td align="center">4</td><td align="center">9.50</td><td align="center">0.20</td><td align="center">10.60</td><td align="center">10.80</td><td align="center">1.30</td><td align="center">6.5</td></tr></tbody></table><p>平均周转时间$t=1.625$,平均带权周转时间$w=5.675$</p><h3 id="5-2-进程调度"><a href="#5-2-进程调度" class="headerlink" title="5.2 进程调度"></a>5.2 进程调度</h3><h4 id="5-2-1-进程调度概述"><a href="#5-2-1-进程调度概述" class="headerlink" title="5.2.1 进程调度概述"></a>5.2.1 进程调度概述</h4><ul><li><p>基本功能</p><ul><li><p>在众多处于就绪状态的进程中,按一定的原则选择一个进程运行</p></li><li><p>调度:组织和维护就绪进程队列</p></li><li><p>分派:处理机空闲的时候,从就绪队列首部选取一个PCB投入运行</p></li></ul></li><li><p>调度时机</p><ul><li>当一个进程从运行态切换成等待态时</li><li>当一个进程从运行态切换成就绪态时</li><li>当一个进程从等待态切换成就绪态时</li><li>当一个进程终止时</li></ul></li><li><p>调度方式</p><ul><li><p>非抢占方式:高优先级的进程<strong>无法</strong>打断正在运行的进程</p></li><li><p>抢占方式:高优先级的进程<strong>可以打断</strong>正在运行的进程</p></li></ul></li></ul><h4 id="5-2-2-进程调度算法-★"><a href="#5-2-2-进程调度算法-★" class="headerlink" title="5.2.2 进程调度算法(★)"></a>5.2.2 进程调度算法(★)</h4><ul><li><p><strong>优先数调度算法</strong>:优先级最高的先被调度</p><ul><li>静态优先数:在进程创建时,根据其需使用的资源、进程类型以及程序运行时间的估计确定</li><li>动态优先数:在进程运行时,动态改变优先数<ul><li>进程使用CPU超过一定数值时,降低优先数</li><li>进程进行I/O操作后,增加优先数</li><li>进程等待时间超过一定数值时,增加优先数</li></ul></li></ul></li><li><p><strong>循环轮转调度算法</strong></p><ul><li>简单循环轮转调度<ul><li>每个进程被调度后,占用一个时间片,时间片用完后转为就绪态并进入就绪队列队尾</li></ul></li><li>可变时间片轮转调度<ul><li>时间片动态选取:过长则轮转时间过长,过短则进程切换开销增加</li><li>调整时间片需要消耗系统时间,调整周期过大则等效固定时间片,过小则调整开销增加</li></ul></li><li>多重时间片轮转调度<ul><li>将就绪进程分为两级或多级,系统相应建立两个或多个就绪进程队列</li><li>较高优先级的队列分配较短的时间片,较低优先级的队列分配较长的时间片</li><li>优先从高级就绪进程队列中选取进程,只有在其为空时,才从较低级的就绪进程队列中选取进程</li></ul></li></ul></li></ul><h2 id="六、主存管理"><a href="#六、主存管理" class="headerlink" title="六、主存管理"></a>六、主存管理</h2><h3 id="6-1-主存管理概述"><a href="#6-1-主存管理概述" class="headerlink" title="6.1 主存管理概述"></a>6.1 主存管理概述</h3><ul><li><p>主存共享方式</p><ul><li><p>大小不同的区域:分区存储管理、段式存储管理</p></li><li><p>大小相等的区域:页式存储管理</p></li><li><p>段页式存储管理</p></li></ul></li><li><p>逻辑组织</p><ul><li><p>一维地址结构</p><ul><li>一个程序是一个连续、线性的地址结构</li><li>确定线性地址空间中的指令地址或操作数地址<strong>只需要一个信息</strong></li></ul></li><li><p>二维地址结构</p><ul><li>一个程序由若干个分段组成,每个分段是一个连续的地址区</li><li>确定线性地址空间中的指令地址或操作数地址<strong>需要两个信息</strong>,一是该信息<strong>所在的分段</strong>,另一个是该信息在<strong>段内的偏移量</strong></li></ul></li></ul></li></ul><h3 id="6-2-主存管理功能"><a href="#6-2-主存管理功能" class="headerlink" title="6.2 主存管理功能"></a>6.2 主存管理功能</h3><ul><li><p>基本概念</p><ul><li><p>物理地址(绝对地址、实地址):计算机主存单元的真实地址</p></li><li><p>主存空间:物理地址的集合所对应的空间</p></li><li><p>逻辑地址(相对地址、虚地址):用户的程序地址 (指令地址或操作数地址)</p></li><li><p>程序地址空间:用户程序所有的逻辑地址集合对应的空间</p></li></ul></li><li><p>主存管理功能</p><ul><li>地址映射:将逻辑地址变换成主存中的物理地址</li></ul></li></ul><table><thead><tr><th align="center">静态地址映射</th><th align="center">动态地址映射</th></tr></thead><tbody><tr><td align="center">在程序装入时确定地址映射关系</td><td align="center">在程序运行时确定地址映射关系</td></tr><tr><td align="center">需软件(重定位装入程序)</td><td align="center">需硬件地址变换机构(重定位寄存器)</td></tr><tr><td align="center">耗费时间长</td><td align="center">映射速度快</td></tr></tbody></table><ul><li><p>主存分配</p><ul><li>构造主存资源信息块(包括等待队列、空闲区队列、主存分配程序)</li><li>制定分配策略</li><li>实施主存分配和回收</li></ul></li><li><p>存储保护</p><ul><li>主存按照区的模式分配给各用户程序使用,每个用户程序必须在给定的存储区域内活动</li><li>上下界保护:设置上界寄存器和下界寄存器,程序访问内存只能使用在上界寄存器和下界寄存器之间的<strong>物理地址</strong>,否则发生越界中断</li><li>基地址、限长保护:基址寄存器表示程序在内存中存储空间从何开始,限长寄存器限制程序访问的<strong>逻辑地址</strong>,其为逻辑地址的最大值</li></ul></li><li><p>主存扩充</p><ul><li>程序的全部代码和数据存放在辅存中;</li><li>将程序当前执行所涉及的那部分程序代码放入主存中;</li><li>程序执行时,当所需信息不在主存,由操作系统和硬件相配合来完成主存从辅存中调入信息,程序继续执行</li></ul></li></ul><h3 id="6-3-分区存储管理-★"><a href="#6-3-分区存储管理-★" class="headerlink" title="6.3 分区存储管理(★)"></a>6.3 分区存储管理(★)</h3><h4 id="6-3-1-静态分区"><a href="#6-3-1-静态分区" class="headerlink" title="6.3.1 静态分区"></a>6.3.1 静态分区</h4><ul><li>把内存预先划分成多个分区,分区大小可以相同或不同,一旦确定则整个系统运行阶段中都保持不变</li><li>一个分区装入一个作业</li></ul><h4 id="6-3-2-动态分区"><a href="#6-3-2-动态分区" class="headerlink" title="6.3.2 动态分区"></a>6.3.2 动态分区</h4><ul><li><p>在<strong>运行程序</strong>的过程中:建立分区,依照用户请求的大小分配分区</p></li><li><p>分区分配数据结构</p><ul><li><p>主存资源信息块M_RIB</p><ul><li>等待队列头指针</li><li>空闲区队列头指针</li><li>主存分配程序入口地址</li></ul></li><li><p>分区描述器PD</p><ul><li>flag:为0表示空闲,为1表示占用</li><li>size:分区大小</li><li>next:如果是空闲区,则为下一个空闲区的首地址;如果是已分配区,该项为0</li></ul></li></ul></li><li><p>分区分配</p><ul><li>寻找空闲块:依申请者所要求的主存区的大小,分区分配程序在自由主存队列中找一个满足用户需要的空闲块</li><li>若找到了所需的空闲区<ul><li>空闲区与要求的大小相等,将该空闲区分配并从队列中摘除</li><li>空闲区大于所要求的的大小,将空闲区分为两部分:一部分成为已分配区,建立已分配区的描述器,剩下部分仍为空闲区</li><li>返回所分配区域的首址</li></ul></li><li>否则,通知申请者无法满足要求</li></ul></li><li><p>分区回收</p><ul><li><p>检查被回收分区在主存中的连接情况</p></li><li><p>如果上/下邻接空闲区,则合并,成为一个新的空闲区</p></li><li><p>若回收分区不与任何空闲区相邻接,则建立一个新的空闲区,加入到空闲队列</p></li></ul></li></ul><h4 id="6-3-3-选择空闲区的放置策略"><a href="#6-3-3-选择空闲区的放置策略" class="headerlink" title="6.3.3 选择空闲区的放置策略"></a>6.3.3 选择空闲区的放置策略</h4><ul><li>首次适应算法<ul><li>将程序放置到<strong>第一个足够装入它的地址最低的</strong>空闲区</li><li>空闲区队列:按空闲区地址由低到高排序</li></ul></li><li>最佳适应算法<ul><li>将程序放置到主存中<strong>与它所需大小最接近的</strong>空闲区中</li><li>空闲区队列:按空闲区大小由小到大排序</li></ul></li><li>最坏适应算法<ul><li>将程序放置到主存中<strong>与它所需大小差距最大的</strong>空闲区中</li><li>空闲区队列:按空闲区大小由大到小排序</li></ul></li></ul><h4 id="6-3-4-分区管理的缺点"><a href="#6-3-4-分区管理的缺点" class="headerlink" title="6.3.4 分区管理的缺点"></a>6.3.4 分区管理的缺点</h4><ul><li>程序必须<strong>整体装入</strong></li><li>需要分配<strong>连续的内存空间</strong></li><li>碎片问题:在已分配的区域里面存在着一些没有被充分利用的空闲区</li></ul><h3 id="6-4-页式存储管理-★"><a href="#6-4-页式存储管理-★" class="headerlink" title="6.4 页式存储管理(★)"></a>6.4 页式存储管理(★)</h3><h4 id="6-4-1-基本概念"><a href="#6-4-1-基本概念" class="headerlink" title="6.4.1 基本概念"></a>6.4.1 基本概念</h4><ul><li>页面(虚页):逻辑地址空间被等分成大小相等的片,称为页面</li><li>主存块(实页):物理地址空间又被分成大小相等的片,称为主存块</li><li>页表:为了实现地址映射,系统建立的记录页面与主存块之间对应关系的地址变换的机构称为页面映像表<ul><li>页表缓冲TLB:CPU中用于存放热页表项的高速缓存,地址变换速度快,但成本较高</li><li>主存区域中的页表:地址变换速度比硬件慢,成本较低</li></ul></li></ul><h4 id="6-4-2-页式地址变换"><a href="#6-4-2-页式地址变换" class="headerlink" title="6.4.2 页式地址变换"></a>6.4.2 页式地址变换</h4><ul><li>地址变换过程<ul><li>CPU给出逻辑地址</li><li>分页机构将逻辑地址分成两部分——高地址页号为P,低地址页内偏移为W</li><li>已知页表基址寄存器指示的首地址PTBR,则PTBR+P就是该虚页对应的页表项地址,获得物理块块号B</li><li>将物理块块号B和页内偏移量W合并,即得到物理块地址</li></ul></li><li>快表TLB(联想存储器)<ul><li>先在快表中查找有没有相关页表项记录,快表是一个独立的硬件,独立于内存之外</li><li>如果快表中没有,只能查找存储在内存中的页表,然后把查出来的页表项记录在快表里面</li></ul></li><li>多级页表<ul><li>间接引用</li><li>页表项中可能存储的不是物理块号,而是下一级页表的首地址</li></ul></li></ul><h4 id="6-4-3-请调页面的机制"><a href="#6-4-3-请调页面的机制" class="headerlink" title="6.4.3 请调页面的机制"></a>6.4.3 请调页面的机制</h4><ul><li><p>两种页式系统</p><ul><li><p>简单页式系统:装入一个程序的<strong>全部页面</strong>才能投入运行</p></li><li><p>请求页式系统:装入一个程序的<strong>部分页面</strong>即可投入运行</p></li></ul></li><li><p>扩充页表项</p><img src="https://picbed.cloudchewie.com/blog/post/Operating-System-Principles-2/Chp1-%E6%89%A9%E5%85%85%E9%A1%B5%E8%A1%A8%E9%A1%B9.png!blogimg" class="" width="400" title="扩充页表项"><ul><li>中断位:表示此页是不是在主存里面,如果是0表示在主存,如果为1表示不在主存</li><li>辅存地址:表示该页在辅存中的地址</li></ul></li><li><p>缺页中断</p><ul><li>当需要访问的逻辑地址所在虚页不在主存时,需要操作系统将该页面调入主存后再进行访问</li><li>缺页中断的处理<ul><li>如果没有空闲的主存块,则需要淘汰某个虚页,该虚页对应的主存块重新分配(如果该虚页发生修改,则需要将修改写入外存)</li><li>从外存中调入所需的页并调整页表</li><li>重新启动被中断的指令</li></ul></li><li>抖动<ul><li>简单地说,导致系统效率急剧下降的主存和辅存之间的频繁的页面置换现象.</li></ul></li></ul></li></ul><h4 id="6-4-4-淘汰策略"><a href="#6-4-4-淘汰策略" class="headerlink" title="6.4.4 淘汰策略"></a>6.4.4 淘汰策略</h4><ul><li><p>在发生缺页中断且没有空闲的主存块时,选择淘汰哪一页的规则称为<strong>淘汰策略</strong></p></li><li><p>扩充页表项</p><img src="https://picbed.cloudchewie.com/blog/post/Operating-System-Principles-2/Chp1-%E5%8A%A0%E5%85%A5%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5%E5%90%8E%E7%9A%84%E6%89%A9%E5%85%85%E9%A1%B5%E8%A1%A8%E9%A1%B9.png!blogimg" class="" width="400" title="加入淘汰策略后的扩充页表项"><ul><li>引用位:该页最近是否被访问,1表示已被访问</li><li>脏位:该页是否被修改,1表示已被修改</li></ul></li><li><p>最佳算法(OPT算法)</p><ul><li>当需要淘汰页面时,所淘汰的页面应是以后不会使用的,或是在最长的时间以后才会使用的页面</li><li>建立在已知未来页面访问顺序的前提下,理想化的算法</li></ul></li><li><p>先进先出淘汰算法(FIFO算法)</p><ul><li>总是淘汰在主存中<strong>停留时间最长(最早进入主存)</strong>的页面</li><li>实现方法<ul><li>建立一个页面进入主存的页号表</li><li>建立一个替换指针,指向最早进入主存的页面</li><li>当需要置换一页时,选择替换指向的那一页,然后调整替换指针的内容</li></ul></li><li>实现方法2<ul><li>在存储分块表中记录页面进入主存的先后次序</li></ul></li></ul></li><li><p>最久未使用算法(LRU算法)</p><ul><li>实现方法:采用页号栈<ul><li>最新被访问的页面入栈,最久未访问的页面在栈底</li><li>要淘汰某个元素了,栈底元素出栈,新的页号入栈</li></ul></li></ul></li><li><p>时钟算法(CLOCK算法、LRU近似淘汰算法)</p><ul><li>为每一个存储块(存储分块表)或页面(页表)设立一个引用位,当访问某页时,就将该页引用位置1</li><li>当内存中无对应数据<ul><li>如果访问位为0即置换并将访问位置1</li><li>如果访问位为1,则不置换,将该访问位置0,然后指针下移,重复第一步</li></ul></li><li>当内存中有对应数据<ul><li>将该数据访问位置1,指针不移动</li></ul></li></ul></li></ul><h3 id="6-5-段式与段页式存储管理"><a href="#6-5-段式与段页式存储管理" class="headerlink" title="6.5 段式与段页式存储管理"></a>6.5 段式与段页式存储管理</h3><ul><li><p>段式系统</p><ul><li><p>段:程序中自然划分的一组逻辑意义完整的信息集合,如代码段、数据段</p></li><li><p>段式地址变换</p><ul><li>将段地址划分为程序地址(s,w),其中s为段号,w为段内偏移</li><li>用段号s检索段表,得到该段的起始地址B</li><li>如果w<0或w≥L则发生主存越界中断;否则(B+w)即为物理地址</li></ul></li></ul></li><li><p>段页式系统</p><ul><li><p>在一个分段内划分页面,就形成了段页式存储管理</p></li><li><p>段页式地址变换</p><ul><li>将段页式地址划分为程序地址(s,p,w),其中s为段号,p为段内页号,w为页内偏移</li><li>根据段号s找到该段的页表基址PTEP</li><li>根据段内页号p查找页表,得到物理页号,与页内偏移合并得到物理地址</li></ul></li></ul></li></ul><h3 id="6-6-Linux存储管理-★"><a href="#6-6-Linux存储管理-★" class="headerlink" title="6.6 Linux存储管理(★)"></a>6.6 Linux存储管理(★)</h3><ul><li><p>地址映射机构MMU</p><ul><li>CPU把虚拟地址送给MMU</li><li>MMU进行地址映射</li><li>MMU把物理地址送给存储器</li></ul></li><li><p>多级页表</p><ul><li><p>二级页表</p><ul><li><p>对于32位虚拟地址空间,假设页面大小为$4K$,页表项大小为$4$字节</p></li><li><p>则一个进程最大拥有$4G$字节内存,即一个进程拥有$\frac{4G}{4k}=2^{20}$个页面</p></li><li><p>其页表项个数为$2^{20}$个,因此该进程的页表占用了$\frac{2^{20}*4}{4K}=2^{10}$个页面</p></li><li><p>即32位的虚拟地址应划分$$(C_{10},P_{10},W_{12})$$,其中C表示页表号,P表示页号,W表示页内偏移</p><img src="https://picbed.cloudchewie.com/blog/post/Operating-System-Principles-2/Chp1-%E4%B8%A4%E7%BA%A7%E9%A1%B5%E8%A1%A8%E5%9C%B0%E5%9D%80%E5%88%92%E5%88%86.png!blogimg" class="" width="400" title="两级页表地址划分"></li><li><p>在进行地址映射时,使用页表号在页目录中查询页表地址,然后使用页号在页表中查询物理页号,物理页号与页内偏移组合后得到物理地址</p></li></ul></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 操作系统 </tag>
</tags>
</entry>
<entry>
<title>操作系统原理笔记(三)</title>
<link href="/posts/2023/03/12/notes/Operating-System-Principles-3/"/>
<url>/posts/2023/03/12/notes/Operating-System-Principles-3/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Operating-System-Principles.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">操作系统原理笔记</div> <div class="tag-link-sitename"> 点击下载操作系统原理笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="七、设备管理"><a href="#七、设备管理" class="headerlink" title="七、设备管理"></a>七、设备管理</h2><h3 id="7-1-设备管理概述"><a href="#7-1-设备管理概述" class="headerlink" title="7.1 设备管理概述"></a>7.1 设备管理概述</h3><ul><li><p>设备分类</p><ul><li>块设备:以<strong>块</strong>为单位传输信息的设备,通常为存储设备,如磁盘、磁带、光驱</li><li>字符设备:以<strong>字符</strong>为单位将信息从计算机外部输入到计算机内或从计算机内输出到外部,如键盘、显示器、打印机</li><li>网络设备:负责<strong>计算机之间</strong>的信息传输,如以太网、无线、蓝牙</li></ul></li><li><p>设备管理的目标</p><ul><li><p>提高设备利用率</p></li><li><p>方便用户使用:提供使用<strong>方便</strong>且<strong>独立于设备</strong>的接口</p></li></ul></li><li><p>设备管理的功能</p><ul><li><p>状态追踪:动态记录设备状态</p></li><li><p>设备分配和回收</p><ul><li>静态分配:程序进入系统的时候就进行分配,退出系统的时候回收全部资源</li><li>动态分配:进程提出设备申请时进行分配,使用完毕立即收回</li></ul></li><li><p>设备控制:实施设备驱动和中断处理的工作</p></li></ul></li><li><p>设备独立性</p><ul><li><p>用户在程序中使用的设备与实际使用的设备无关——在用户程序中,只使用逻辑设备名</p></li><li><p>逻辑设备名:用户自己指定的设备名,可以更改</p></li><li><p>物理设备名:系统提供的设备的标准名称,是永久的,可以更改的</p></li><li><p>实现方式</p><ul><li>在高级语言中用软通道实现</li><li>在批处理系统中,用联接说明语句来定义</li><li>在交互系统中,用指派命令来定义</li></ul></li></ul></li><li><p>设备控制块:记录设备的硬件特性,连接和使用情况的一组数据</p><ul><li>设备名——设备的物理名称</li><li>设备属性</li><li>命令转换表——包含设备特定的I/O例程地址,表示设备能执行何种I/O操作,若不具备相应功能则填-1</li></ul></li></ul><h3 id="7-2-I-x2F-O控制"><a href="#7-2-I-x2F-O控制" class="headerlink" title="7.2 I/O控制"></a>7.2 I/O控制</h3><h4 id="7-2-1-I-x2F-O控制方式"><a href="#7-2-1-I-x2F-O控制方式" class="headerlink" title="7.2.1 I/O控制方式"></a>7.2.1 I/O控制方式</h4><ul><li>循环测试I/O方式</li><li>I/O中断方式</li><li>DMA方式</li><li>通道方式</li></ul><h4 id="7-2-2-I-x2F-O子系统"><a href="#7-2-2-I-x2F-O子系统" class="headerlink" title="7.2.2 I/O子系统"></a>7.2.2 I/O子系统</h4><ul><li><p>在应用程序提供I/O应用接口</p></li><li><p>每个通用设备类型都通过一组标准函数(以及接口)来访问.</p></li><li><p>设备驱动程序</p><ul><li><p>能直接控制设备运转的程序,它根据各类设备的特点和性能来编写</p></li><li><p>每一类设备有一个相应的设备驱动程序,能控制同类中多台物理设备同时工作</p></li><li><p>设备I/O完成或出错时产生中断,由该类设备的中断处理程序处理</p></li></ul></li><li><p>设备处理进程</p><ul><li><p>为每一类设备设置一个设备处理进程</p></li><li><p>没有I/O请求时,该进程睡眠</p></li><li><p>当有I/O请求来到时,该进程被唤醒,进行设备驱动工作</p></li></ul></li><li><p>I/O接口程序</p><ul><li><p>首先把逻辑设备转化为物理设备</p></li><li><p>合法性检查,这个设备能否执行这个操作</p></li><li><p>形成I/O请求块,发送给设备处理进程</p></li></ul></li><li><p>处理顺序</p><ul><li><p>用户进程请求IO</p></li><li><p>首先进入I/O过程</p></li><li><p>由I/O过程进入I/O处理进程</p></li><li><p>I/O处理进程启动I/O设备进行I/O操作,进入等待状态</p></li><li><p>I/O设备执行完I/O操作后进入中断唤醒I/O处理进程</p></li><li><p>I/O处理进程则唤醒调用该I/O的用户进程</p></li></ul></li></ul><h3 id="7-3-缓冲技术-★"><a href="#7-3-缓冲技术-★" class="headerlink" title="7.3 缓冲技术(★)"></a>7.3 缓冲技术(★)</h3><h4 id="7-3-1-缓冲技术概述"><a href="#7-3-1-缓冲技术概述" class="headerlink" title="7.3.1 缓冲技术概述"></a>7.3.1 缓冲技术概述</h4><ul><li><p>两种不同速度的设备之间传输信息的平滑传输过程</p></li><li><p>缓冲的类别</p><ul><li>硬件缓冲:使用缓冲器——用来暂时存放数据的一种存储装置</li><li>软件缓冲:在I/O操作期间用来临时存放I/O数据的一块存储区域</li></ul></li><li><p>缓冲的目的</p><ul><li>处理数据流的生产者与消费者速度差异</li><li>协调传输数据大小不一致的设备</li></ul></li></ul><h4 id="7-3-2-缓冲类型"><a href="#7-3-2-缓冲类型" class="headerlink" title="7.3.2 缓冲类型"></a>7.3.2 缓冲类型</h4><ul><li><p>单缓冲</p><ul><li><p>读设备得数据</p><ul><li>首先获得一个空的缓冲区</li><li>设备把物理记录送到缓冲区中</li><li>用户请求数据时,系统将依据逻辑记录特性从缓冲区提取数据并发送到用户进程存储区</li><li>如果用户请求数据且缓冲区中没有数据时,进程被迫等待</li></ul></li><li><p>写数据到设备</p><ul><li>首先获得一个空的缓冲区</li><li>用户将一个逻辑记录从进程存储区送到缓冲区中</li><li>当缓冲区写满时,系统将缓冲区内容作为物理记录写到设备上</li><li>当进程输出信息且缓冲区已满时 ,进程被迫等待</li></ul></li></ul></li><li><p>双缓冲</p><ul><li><p>两个缓冲区</p></li><li><p>数据输入</p><ul><li>输入设备首先填满buf1</li><li>进程从buf1提取数据的时候,输入设备填满buf2;当缓冲区一个空,一个满的时候就可以交换</li><li>即进程提取一个缓冲区,设备往另外一个缓冲区输入数据</li></ul></li><li><p>数据输出</p><ul><li>进程首先填满buf1</li><li>设备从buf1提取数据时,进程往buf2输出数据;当缓冲区一个空,一个满的时候就交换</li></ul></li></ul></li><li><p>环形缓冲</p><ul><li>缓冲区构成一个环形链表,有读指针和写指针</li><li>读写元素分别从读指针和写指针写数据</li></ul></li><li><p>缓冲池</p><ul><li>将系统内所有的缓冲区统一管理起来,形成能用于输入/输出的缓冲池</li><li>缓冲池通常由若干大小相同的缓冲区组成,是系统的公用资源,任何进程都可以申请使用缓冲池中的各个缓冲区</li></ul></li></ul><h4 id="7-3-3-UNIX缓冲区管理"><a href="#7-3-3-UNIX缓冲区管理" class="headerlink" title="7.3.3 UNIX缓冲区管理"></a>7.3.3 UNIX缓冲区管理</h4><ul><li>缓冲管理数据结构<ul><li>缓存数组:含有磁盘上的数据的存储器数组</li><li>缓存首部:描述缓冲区特性的数据结构<ul><li>设备号,使用该缓冲区的设备号</li><li>块号,由设备号指出的设备上相对于第0块的块号</li><li>状态信息flag</li><li>指向数据区域的指针</li><li>设备缓冲区队列前后向指针</li><li>空闲缓冲区队列前后向指针</li></ul></li><li>队列结构<ul><li>设备缓冲区队列(b链):与某类设备有关的所有缓冲区</li><li>空闲缓冲区队列(av链):可供重新分配使用的空闲缓冲区组成的队列</li></ul></li></ul></li><li>缓冲管理算法<ul><li>当某个缓冲区被分配用于读/写某设备时:置B_ BUSY=1,位于b链上,不在av链上;</li><li>当读/写操作结束时:释放缓冲区,置B_BUSY=0,仍留在b链上,并送入av链尾;</li><li>若进程需要的信息在缓冲区中时:在该设备的b链上找到,置B_BUSY=1;从av链上摘除,使用完后,送入av链队尾;</li><li>对空闲缓冲区队列的处理:当需要一个空闲缓冲区时,总是取av链的首元素;一个使用过的缓冲区释放时,送入av链队尾——实现了精确的LRU算法;</li><li>对延迟写的处理:当一个具有延迟写标记的缓冲区移到av链头,要用于分配时,立即进行写操作。从av链上摘除,使用完后又送入av头部</li></ul></li></ul><h3 id="7-4-设备分配"><a href="#7-4-设备分配" class="headerlink" title="7.4 设备分配"></a>7.4 设备分配</h3><h4 id="7-4-1-分配算法-★"><a href="#7-4-1-分配算法-★" class="headerlink" title="7.4.1 分配算法(★)"></a>7.4.1 分配算法(★)</h4><ul><li><p>先来先服务</p></li><li><p>优先级高者优先</p></li><li><p>特定设备分配算法——磁盘调度算法</p><ul><li><p>磁盘访问时间</p><ul><li><p>寻道时间$T_s$</p><ul><li>把磁臂(磁头)移动到指定磁道上所经历的时间</li><li>启动磁臂的时间s与磁头移动n条磁道所花费的时间之和,即$T_s=s+m\times n$</li><li>其中,m是与磁盘驱动器速度有关的常数;对一般磁盘,m=0.2;对高速磁盘,m ≤ 0.1,磁臂的启动时间约为2ms</li></ul></li><li><p>旋转延迟时间$T_{\tau}$</p><ul><li>指定扇区移动到磁头下面所经历的时间</li><li>对于硬盘,旋转速度约为5400r/min,每转需时11.1ms, 平均旋转延迟时间为5.55 ms</li><li>对于软盘,旋转速度为300r/min或600r/min,平均旋转延迟时间为50~100ms</li></ul></li><li><p>传输时间$T_t$</p><ul><li>把数据从磁盘读出或向磁盘写入数据所经历的时间</li><li>$T_t$的大小与每次所读/写的字节数b和旋转速度有关,即$T_t=\frac{b}{rN}$</li><li>其中,r为磁盘每秒钟的转数,N为一条磁道上的字节数</li></ul></li><li><p>访问时间:$T_a=T_s+T_{\tau}+T_t$</p></li></ul></li></ul></li><li><p>先来先服务FCFS</p><ul><li>按进程请求访问磁盘的先后次序进行调度<ul><li>假设有如下请求序列: 98, 183, 37, 122, 14, 124, 65, 67,磁头当前的位置在53</li></ul></li><li>寻道序列如下</li></ul></li></ul><table><thead><tr><th align="center">下磁道</th><th align="center">移道数</th></tr></thead><tbody><tr><td align="center">98</td><td align="center">45</td></tr><tr><td align="center">183</td><td align="center">85</td></tr><tr><td align="center">37</td><td align="center">146</td></tr><tr><td align="center">122</td><td align="center">85</td></tr><tr><td align="center">14</td><td align="center">108</td></tr><tr><td align="center">124</td><td align="center">110</td></tr><tr><td align="center">65</td><td align="center">59</td></tr><tr><td align="center">67</td><td align="center">2</td></tr><tr><td align="center">总道数</td><td align="center">640</td></tr><tr><td align="center">平均</td><td align="center">80</td></tr></tbody></table><ul><li><p>最短寻道时间优先SSTF</p><ul><li>选择从当前磁头位置所需寻道时间最短的请求</li><li>寻道序列如下</li></ul></li></ul><table><thead><tr><th align="center">下磁道</th><th align="center">移道数</th></tr></thead><tbody><tr><td align="center">65</td><td align="center">12</td></tr><tr><td align="center">67</td><td align="center">2</td></tr><tr><td align="center">37</td><td align="center">30</td></tr><tr><td align="center">14</td><td align="center">23</td></tr><tr><td align="center">98</td><td align="center">84</td></tr><tr><td align="center">122</td><td align="center">24</td></tr><tr><td align="center">124</td><td align="center">2</td></tr><tr><td align="center">183</td><td align="center">59</td></tr><tr><td align="center">总道数</td><td align="center">236</td></tr><tr><td align="center">平均</td><td align="center">29.5</td></tr></tbody></table><ul><li><p>扫描算法(SCAN、电梯算法)</p><ul><li>磁头从磁盘的一端开始向另一端移动,沿途响应访问请求,直到到达了磁盘的另一端,此时磁头反向移动并继续响应服务请求</li><li>寻道序列如下</li></ul></li></ul><table><thead><tr><th align="center">下磁道</th><th align="center">移道数</th></tr></thead><tbody><tr><td align="center">65</td><td align="center">12</td></tr><tr><td align="center">67</td><td align="center">2</td></tr><tr><td align="center">98</td><td align="center">31</td></tr><tr><td align="center">122</td><td align="center">24</td></tr><tr><td align="center">124</td><td align="center">2</td></tr><tr><td align="center">183</td><td align="center">59</td></tr><tr><td align="center">37</td><td align="center">146</td></tr><tr><td align="center">14</td><td align="center">23</td></tr><tr><td align="center">总道数</td><td align="center">299</td></tr><tr><td align="center">平均</td><td align="center">37.4</td></tr></tbody></table><ul><li><p>循环扫描算法(CSCAN)</p><ul><li>规定磁头从磁盘的一端开始向另一端单向移动,沿途响应访问请求</li><li>寻道序列如下</li></ul></li></ul><table><thead><tr><th align="center">下磁道</th><th align="center">移道数</th></tr></thead><tbody><tr><td align="center">65</td><td align="center">12</td></tr><tr><td align="center">67</td><td align="center">2</td></tr><tr><td align="center">98</td><td align="center">31</td></tr><tr><td align="center">122</td><td align="center">24</td></tr><tr><td align="center">124</td><td align="center">2</td></tr><tr><td align="center">183</td><td align="center">59</td></tr><tr><td align="center">14</td><td align="center">169</td></tr><tr><td align="center">37</td><td align="center">23</td></tr><tr><td align="center">总道数</td><td align="center">322</td></tr><tr><td align="center">平均</td><td align="center">40.3</td></tr></tbody></table><h4 id="7-4-2-分配策略"><a href="#7-4-2-分配策略" class="headerlink" title="7.4.2 分配策略"></a>7.4.2 分配策略</h4><ul><li>独享分配:分配独享设备——在一个作业整个运行期间占用的设备</li><li>共享分配:分配多个作业、进程共同使用的共享设备</li><li>虚拟分配<ul><li>所谓虚拟技术,是在一类物理设备上模拟另一类物理设备的技术,是将独占设备转化为共享设备的技术。</li><li>通常把用来代替独占型设备的那部分外存空间 (包括有关的控制表格)称为虚拟设备。</li><li>进程先把元素写入位于磁盘中的虚拟设备</li><li>然后虚拟设备分配管理器再把磁盘中的虚拟设备数据写入物理设备</li></ul></li><li>SPOOLING(一种实例虚拟设备分配策略)<ul><li>预输入<ul><li>应用程序需要数据之前,OS已经把所需要的数据放入输入井中存放,应用程序可以直接从输入井获取数据</li></ul></li><li>缓输出<ul><li>应用程序执行的时候,将输出数据写入输出井中,当应用程序执行完毕后,OS将输出井的数据输出</li></ul></li><li>利用通道和中断技术,在主机控制之下,由通道完成输入输出工作。系统提供一个软件系统 (包括预输入程序、缓输出程序、井管理程序、预输入表、缓输出表)。它提供输入收存和输出发送的功能,使外部设备可以并行操作。这一软件系统称为SPOOLING系统。</li><li>基础<ul><li>辅存空间</li><li>通道和中断</li><li>数据结构</li><li>软件<ul><li>预输入,缓输出,井管理程序</li></ul></li></ul></li></ul></li></ul><h2 id="八、文件系统"><a href="#八、文件系统" class="headerlink" title="八、文件系统"></a>八、文件系统</h2><h3 id="8-1-文件系统概述"><a href="#8-1-文件系统概述" class="headerlink" title="8.1 文件系统概述"></a>8.1 文件系统概述</h3><ul><li><p>文件的概念</p><ul><li><p>在逻辑上上具有完整意义的信息集合,以文件名作为标识</p></li><li><p>文件是具有符号名的信息项(数据项、记录)的集合</p></li></ul></li><li><p>文件的属性</p><ul><li><p>文件名:每个文件有一个给定的名字,包括文件符号名和内部标识符</p><ul><li>用户使用文件符号名来标记文件</li><li>系统使用内部标志符来标记文件</li></ul></li><li><p>文件拓展名:标记文件的使用特征</p></li><li><p>文件属性:包含文件类别、保护级等信息,如文件大小、文件所有者、文件创建时间、最后修改时间</p></li></ul></li><li><p>文件系统</p><ul><li><p>文件系统是操作系统中负责管理和存取文件信息的软件机构</p></li><li><p>组成</p><ul><li>管理文件所需的数据结构,如目录表、文件控制块、存储分配表</li><li>管理程序</li></ul></li><li><p>功能</p><ul><li>用户视角:“按名存取”的功能</li><li>系统视角:辅存空间管理、构造文件结构、文件共享、存取文件的方法、文件保护、一组文件操作命令</li></ul></li></ul></li><li><p>文件组织两种结构</p><ul><li><p>逻辑结构(用户角度)</p></li><li><p>物理结构(系统角度):在物理存储器上的表现形式</p></li><li><p>逻辑记录:文件中按信息在逻辑上的独立含义来划分的信息单位,对文件进行存取操作的基本单位</p></li><li><p>物理记录:在存储介质上,由连续信息组成的一个区域称为磁盘块,也可以叫物理记录</p></li></ul></li></ul><h3 id="8-2-文件的逻辑结构与存取方法"><a href="#8-2-文件的逻辑结构与存取方法" class="headerlink" title="8.2 文件的逻辑结构与存取方法"></a>8.2 文件的逻辑结构与存取方法</h3><ul><li><p>文件的逻辑结构</p><ul><li><p>流式文件</p><ul><li>流式文件是相关的有序字符的集合,是<strong>无结构</strong>的,仅仅是一堆字节组成的字符的集合</li><li>存取方式:按信息的个数或以特殊字符为界进行存取</li></ul></li><li><p>记录式文件</p><ul><li>记录式文件是一种<strong>有结构</strong>的文件,在逻辑上被看成一组连续顺序的记录的集合</li></ul></li></ul></li><li><p>文件存取方法</p><ul><li><p>顺序存取</p><ul><li>后一次存取总是在前一次存取的基础上进行的</li><li>只有取完第一个才能取第二个</li><li>不必给出具体的存取位置</li></ul></li><li><p>随机存取</p><ul><li>用户以任意次序请求某个记录,可以随便取第n个元素</li><li>需指出起始存取位置(例如记录号)</li></ul></li></ul></li></ul><h3 id="8-3-文件的物理结构-★"><a href="#8-3-文件的物理结构-★" class="headerlink" title="8.3 文件的物理结构(★)"></a>8.3 文件的物理结构(★)</h3><ul><li><p>连续文件</p><ul><li><p>一个文件分配在磁盘连续区域的物理块</p></li><li><p>文件在文件目录里记录的信息:文件符号名,文件的第一个磁盘块块号,文件占据的磁盘块数</p></li></ul></li><li><p>串联文件</p><ul><li>文件结构由按顺序串联的若干个物理块组成,每个物理块的最后一个字作为<strong>链接字</strong>用来指示后续物理块的物理地址</li><li>文件在文件目录里记录的信息:文件符号名,文件的第一个磁盘块块号(剩下的磁盘块号通过每个磁盘块的链接字指示)</li><li>文件分配表FAT<ul><li>把串联文件中的链接字集中在一个结构中,既保持了串联文件的优点,也克服了其随机存取速度慢的缺点</li><li>即以链接方式存储文件的系统中记录磁盘分配和跟踪空白磁盘块(簇)的数据结构</li><li>该表在文件系统格式化后产生,共包含N个表项,每个表项对应一个簇,编号从0开始直至N-1(N为磁盘中簇的总数)</li><li>每个表项中的内容为存放文件数据的下一个簇的簇号。</li><li>文件的首地址(第一个簇号)存放在目录中,从目录中找到文件的首地址后,就能找到文件在磁盘上的所有存放地址</li><li>在FAT表中,全0表示空闲簇,全1表示文件结尾簇,其余均表示文件的下一簇</li></ul></li></ul></li><li><p>索引文件</p><ul><li><p>系统为每个文件建立逻辑块号与物理块号的对照表,这张表称为该文件的索引表</p></li><li><p>索引文件由数据块和索引表构成</p></li><li><p>组织类型</p><ul><li>直接索引:索引表就是存储数据的物理块块号</li><li>一级间接索引<ul><li>文件目录项中的表项——一级间接索引表块的块号</li><li>一级间接索引表块的表项——文件逻辑记录所在的磁盘块号</li></ul></li><li>二级间接索引<ul><li>文件目录项中的表项——二级间接索引表块的块号</li><li>二级间接索引表块的表项——一级间接索引表块的块号</li><li>一级间接索引表块的表项——文件逻辑记录所在的磁盘块号</li></ul></li></ul></li></ul></li></ul><h3 id="8-4-文件目录-★"><a href="#8-4-文件目录-★" class="headerlink" title="8.4 文件目录(★)"></a>8.4 文件目录(★)</h3><ul><li><p>文件控制块FCB</p><ul><li>文件系统为每个文件建立的唯一的数据管理结构</li><li>文件标识和控制信息:文件名、用户名、文件权限 、文件类型</li><li>文件逻辑结构信息</li><li>文件物理结构信息</li><li>文件使用信息</li><li>文件管理信息</li></ul></li><li><p>文件目录</p><ul><li><p>文件目录是记录文件控制块FCB信息的数据结构</p></li><li><p>文件目录项:记录一个文件的信息,存在两种两种目录项</p><ul><li><p>普通文件的FCB</p></li><li><p>子目录的目录文件的FCB</p><blockquote><p>文件目录在系统里面是以<strong>目录文件</strong>的形式存在的,是一个具体的文件</p><p>目录文件至少包含两个目录项</p><ul><li>当前目录项</li><li>父目录项</li></ul></blockquote></li></ul></li></ul></li><li><p>一级文件目录</p><ul><li>已建立的所有文件的文件名、存放地址和有关的说明信息都放在一张表中</li><li>不允许文件重名</li><li>在多用户环境中,易出现重名问题</li></ul></li><li><p>二级文件目录</p><ul><li>将文件目录分成<strong>主目录和用户文件目录</strong>两级</li><li>每个用户建立一个用户文件目录,登记该用户建立的所有文件的相关信息</li><li>主目录登记系统中各个用户文件目录的相关信息</li></ul></li><li><p>树形文件目录</p><ul><li>目录文件就包含了这个目录下面所有数据文件和目录文件对应的文件目录项</li><li>数据文件一定在树叶上</li><li>树形结构中每一层就是一个目录</li></ul></li><li><p>文件路径名:多级目录中,文件的路径名是由根目录到该文件的通路上所有目录文件符号名和该文件的符号名组成的字符串,相互之间用分隔符分隔</p></li></ul><h3 id="8-5-文件存储空间管理"><a href="#8-5-文件存储空间管理" class="headerlink" title="8.5 文件存储空间管理"></a>8.5 文件存储空间管理</h3><ul><li>空闲文件目录<ul><li>将所有空闲块记录在一个表中,即空闲块表</li><li>表项内容:起始块号,空闲块个数</li></ul></li><li>空闲块链:把所有空闲块链成一个链</li><li>位示图<ul><li>用一串二进制位反映磁盘空间中分配使用情况</li><li>每个物理块对应一位,已分配物理块为1,否则为0</li><li>申请物理块时,可以在位示图中查找为0的位,返回对应物理块号</li><li>归还时,将对应位置为0</li></ul></li></ul><h3 id="8-6-文件的共享与安全-★"><a href="#8-6-文件的共享与安全-★" class="headerlink" title="8.6 文件的共享与安全(★)"></a>8.6 文件的共享与安全(★)</h3><ul><li><p>文件共享:某一个或者某一部分的文件让多个用户共同使用</p></li><li><p>文件安全:文件本身不得被未经文件所有者授权的任何用户存取</p></li><li><p>保护方法:对用户的权限进行验证,是指用户在存取文件之前,需要检查用户的存取权限是否符合规定</p></li><li><p>文件查找</p><ul><li><p>当前目录</p><ul><li>当前目录是当前用户正在使用的文件所在的目录</li><li>当指定当前目录后,用户对文件的所有访问都是相对于”当前目录“进行的</li></ul></li><li><p>链接技术</p><ul><li>一个目录中的一个表目直接指向另一个目录表目对应的物理位置</li></ul></li><li><p>UNIX/Linux的链接</p><ul><li><p>硬链接</p><ul><li>不同的目录项引用同一个文件,$I$结点相同</li><li>在索引文件中增加链接计数,用于记录共享数量</li><li>硬链接与源文件等价,两个文件的物理结构项一样</li><li>不能链接目录文件</li><li>删除源文件后,硬链接文件可照常使用</li><li>硬链接只限于本文件系统</li><li>硬链接可以加快文件查找速度</li></ul></li><li><p>软链接/符号链接</p><ul><li>创建一个LINK类型的新文件,文件中仅包含被链接文件的路径名</li><li>删除源文件后,软链接文件的操作会失败</li><li>软链接可以链接到处于不同文件系统的文件及目录</li><li>软链接不可以加快文件查找速度</li></ul></li></ul></li></ul></li></ul><h3 id="8-7-文件操作与文件备份"><a href="#8-7-文件操作与文件备份" class="headerlink" title="8.7 文件操作与文件备份"></a>8.7 文件操作与文件备份</h3><ul><li><p>文件的操作</p><ul><li><p>文件的打开</p><ul><li>首先获得文件路径名</li><li>按照名字查找文件目录结构获得目录项找到FCB(注意,只用找到FCB就可以,对应的数据块不需要)</li><li>存入活跃文件目录表</li><li>建立文件读写状态信息表,将访问指针指向文件首</li></ul></li><li><p>文件关闭</p><ul><li>检查参数,获得fd;</li><li>在打开文件表和文件读写状态信息表中把对应文件占用的空间释放</li><li>如果“活跃文件目录表”中文件控制块不再使用,则释放该文件控制块所占的内存空间</li></ul></li><li><p>文件创建</p><ul><li>检查参数合法性</li><li>建立一个文件控制块,并在目录表中建目录项。</li><li>将参数填入文件控制块</li><li>分配文件所存放的外存空间(也可在写数据时分配),将文件物理存储信息填入文件控制块中</li></ul></li><li><p>文件删除</p><ul><li>检查参数,得到文件名(路径名)</li><li>按名查找文件目录结构得到目录项,找到文件的文件控制块</li><li>按文件控制块中的定位信息(如索引表)释放文件所占外存空间</li><li>从文件目录结构中删除文件控制块及目录项</li></ul></li></ul></li><li><p>文件相关的表</p><ul><li><p>进程控制块里面有有打开文件表,记录这个进程打开了什么文件</p></li><li><p>对于进程的打开的许多文件有一个读写状态信息表,记录进程读或者写一个文件写到文件的哪里了</p></li><li><p>活跃文件目录表,就是记录所有打开过的文件的FCB,读写状态信息表中就指向活跃文件目录表</p></li></ul></li><li><p>文件备份</p><ul><li><p>周期性转储:过一个周期就把存储器所有内容存一遍</p></li><li><p>增量性转储:以文件为单位,定期转储上次转储后改过的新文件</p></li></ul></li></ul><h3 id="8-8-UNIX文件系统-★"><a href="#8-8-UNIX文件系统-★" class="headerlink" title="8.8 UNIX文件系统(★)"></a>8.8 UNIX文件系统(★)</h3><h4 id="8-8-1-文件系统概述"><a href="#8-8-1-文件系统概述" class="headerlink" title="8.8.1 文件系统概述"></a>8.8.1 文件系统概述</h4><ul><li><p>文件特点</p><ul><li>树型文件目录结构</li><li>可安装拆卸的文件系统</li><li>文件是无结构的字符流式文件</li><li>将外部设备与文件一样对待</li></ul></li><li><p>索引表</p><ul><li><p>索引文件结构</p><ul><li>文件索引节点<ul><li>把文件目录项中除了名字以外的信息全部存放到一个磁盘的数据块上,这种数据块称为磁盘索引节点,简称$I$节点</li><li>包含文件所有者、文件类型、文件存取许可权、文件链接数目、文件长度、地址索引表</li><li>每个$I$节点的大小为$128$字节</li><li>一个$UNIX$文件系统中能够创建的文件数量,既受到索引节点区中$I$节点数量的约束,又受到数据区中数据块数量的约束</li></ul></li></ul></li><li><p>地址索引表</p><ul><li>UNIX第七版:使用地址索引表$i_addr[8]$描述物理结构<ul><li><ul><li>$i_addr[0]-i_addr[7]$为直接索引表</li><li>最大为$8\times$磁盘块大小</li></ul></li><li>大型文件<ul><li>$i_addr[0]-i_addr[6]$为一级间接索引表</li><li>最大为$7\times256\times$磁盘块大小</li></ul></li><li>巨型文件<ul><li>$i_addr[0]-i_addr[6]$为一级间接索引表</li><li>$i_addr[7]$为二级间接索引表</li><li>最大为$(7\times256+256\times256)\times$磁盘块大小</li></ul></li></ul></li><li>UNIX V:使用地址索引表$i_addr[13]$描述物理结构<ul><li>前10个用于直接索引</li><li>第11个用于一级间接索引</li><li>第12个用于二级间接索引</li><li>第13个用于三级间接索引</li><li>最大为$(10+256+256^2+256^3)\times $磁盘块大小</li></ul></li></ul></li></ul></li><li><p>文件目录结构</p><ul><li>每个目录项包含16个字节,第1、2字节为相应文件的辅存$I$节点号,后14个字节为文件名</li></ul></li><li><p>树型目录结构</p><ul><li><p>每个文件系统都有一个根目录文件,它的辅存i节点是相应文件存储设备上辅存索引区中的第一个。</p></li><li><p>文件目录项存储的是索引节点的节点号,要获得文件,要先打开索引节点,在索引节点中根据节点地址寻找文件本身的数据.(或者是目录或者是数据)</p></li></ul></li><li><p>打开文件的结构</p><ul><li><p>活动i节点表</p><ul><li>当执行打开文件操作时,将文件辅存$I$节点的有关信息拷贝到主存,形成活动$I$节点表,他由若干个活动$I$节点组成</li></ul></li><li><p>系统打开文件表</p><ul><li>一个文件可以被不同进程以相同或不同路径名打开,因此通过构造系统打开文件表记录所有进程打开过什么文件<ul><li>读写标志:表示文件的打开模式,读或者写</li><li>引用计数:多少个进程用该读写标志打开该文件</li><li>指向该文件对应的主存索引节点</li></ul></li></ul></li><li><p>用户文件描述符表</p><ul><li><strong>每个进程里面的结构</strong>,用来记录这个进程用何种方式打开过何种文件,会指向系统打开文件表中的一个表项</li></ul></li><li><p>子进程共享父进程的<strong>“系统打开文件表项”</strong> ,该表项的文件打开计数f_count加1,子进程直接使用父进程open()操作返回的文件描述符fd即可访问该文件</p></li><li><p>父进程的close()操作不影响子进程对该文件的使用</p></li><li><p>父子进程独立运行后,各自open的文件就不再共享</p></li></ul></li></ul><h4 id="8-8-2-文件存储空间的管理"><a href="#8-8-2-文件存储空间的管理" class="headerlink" title="8.8.2 文件存储空间的管理"></a>8.8.2 文件存储空间的管理</h4><ul><li><p>引导块:大小为一个磁盘块,包含引导程序</p></li><li><p>管理块:记录文件系统各种数据</p><ul><li>直接管理的空闲块数s_nfree</li><li>空闲块号栈s_free[]</li><li>直接管理的空闲$I$节点数s_ninode</li><li>空闲$I$节点号栈s_inode[]</li></ul></li><li><p>空闲磁盘块的管理——成组链接法</p><ul><li><p>将空闲表和空闲链两种方法相结合</p></li><li><p>系统初启时,文件存储区是空闲的;将空闲块从尾倒向前,每100块分为一组 (最后一组为99块),每一组的最后一块作为索引表,用来登记下一组100块的物理块号和块数;最前面一组(可能不足100块)的物理块号和块数存放在管理块的s_free[100]和s_nfree中</p></li><li><p>分配算法</p><ul><li>s_nfree-1</li><li>如果s_nfree为0了,就把s_free[0]也就是下一个管理块载入到内存中</li></ul></li><li><p>回收算法</p><ul><li>s_nfree+1</li><li>如果达到了100,就把当前管理块释放到磁盘中,然后初始化,s_free[0]为原来释放到的那个磁盘块块号</li></ul></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 操作系统 </tag>
</tags>
</entry>
<entry>
<title>数据库系统概论笔记(一)</title>
<link href="/posts/2023/02/22/notes/Introduction-to-Database-System-1/"/>
<url>/posts/2023/02/22/notes/Introduction-to-Database-System-1/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Introduction-to-Database-Systems.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">数据库系统概论笔记</div> <div class="tag-link-sitename"> 点击下载数据库系统概论笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="一、绪论"><a href="#一、绪论" class="headerlink" title="一、绪论"></a>一、绪论</h2><h3 id="1-1-数据模型"><a href="#1-1-数据模型" class="headerlink" title="1.1 数据模型"></a>1.1 数据模型</h3><h4 id="1-1-1-数据模型概述"><a href="#1-1-1-数据模型概述" class="headerlink" title="1.1.1 数据模型概述"></a>1.1.1 数据模型概述</h4><ul><li>数据模型是严格定义的一组概念,精确的描述了系统的静态特性、动态特性和完整性约束</li><li>组成要素<ul><li>数据结构:描述系统的<strong>静态特性</strong>,即组成数据库的对象类型</li><li>数据操作:描述系统的<strong>动态特性</strong>,即对数据库中对象的实例允许执行的操作的集合,包括操作及操作规则</li><li>数据的约束条件:数据的约束条件是<strong>完整性规则</strong>的集合,规定数据库状态及状态变化所应满足的条件,以保证数据的正确、有效</li></ul></li><li>类型<ul><li>概念模型</li><li>层次模型</li><li>网状模型</li><li>关系模型</li></ul></li></ul><h4 id="1-1-2-实体-联系模型(E-R模型)"><a href="#1-1-2-实体-联系模型(E-R模型)" class="headerlink" title="1.1.2 实体-联系模型(E-R模型)"></a>1.1.2 实体-联系模型(E-R模型)</h4><ul><li>基本概念<ul><li>世界是由一组称作<strong>实体</strong>的基本对象和这些对象之间的<strong>联系</strong>构成的</li><li>实体(Entity):客观存在并可相互区别的人、事物、事件和概念</li><li>属性(Attribute):实体具有的特性</li><li>码(Key、实体标识符):唯一标识实体的属性集</li><li>域(Domain):属性的取值范围</li><li>实体型(Entity Type):用实体名及其属性名集合来抽象刻画同类实体</li><li>实体集(Entity Set):同型实体的集合,如全体学生,全部的系</li><li>联系(Relationship):实体(型)内部的联系(组成实体的各属性间);实体(型)之间的联系(不同的实体集之间)</li></ul></li><li>图形表示<ul><li>矩形框 – 实体型</li><li>菱形框 – 联系类型</li><li>椭圆形框 – 实体型/联系类型的属性</li><li>直线 – 实体型与联系之间用直线相连,线旁标注联系的种类(1:1, 1:N, M:N)</li><li>实体集属性中<strong>作为主码的一部分的属性用下划线</strong>来标明</li><li>在1:N的联系中,联系集的主码是n端的主码</li></ul></li></ul><h4 id="1-1-3-由E-R图到数据库设计"><a href="#1-1-3-由E-R图到数据库设计" class="headerlink" title="1.1.3 由E-R图到数据库设计"></a>1.1.3 由E-R图到数据库设计</h4><ul><li>E-R图的设计要点<ul><li>确定实体:实体名、实体属性、实体码</li><li>确定联系:存在性联系、功能性联系、事件联系</li></ul></li><li>E-R图向关系模型的转换<ul><li>实体→关系</li><li>属性→关系的属性</li><li>一对一联系→新的关系,属性为双方的码</li><li>一对一联系→与某一端关系合并,合并后在该端加入另一端关系的码和联系本身的属性,其码不变</li><li>多对多联系→新的关系,其属性为与该联系相连的各实体的码以及联系本身的属性,其码是与该联系相连的各实体的码的组合</li><li>一对多联系→新的关系,其属性为与该联系相连的各实体的码以及联系本身的属性,其码是<strong>n端实体的码</strong></li><li>一对多联系→与n端关系合并,合并后在n端关系加入1端关系的码和联系本身的属性,<strong>n端关系的码不变</strong></li><li>三个或三个以上实体间的多元联系→新的关系,其属性为与该多元联系相连的各实体的码以及联系本身的属性,其码是与该联系相连的各实体的码的组合</li><li>合并具有相同码的关系模式,并去除同义属性</li></ul></li><li>从关系模型到数据库实施<ul><li>定义数据库结构:表结构、视图</li><li>装载数据</li><li>编制与调试应用程序</li><li>数据库试运行</li><li>数据库运行与维护</li></ul></li></ul><h4 id="1-1-4-关系模型"><a href="#1-1-4-关系模型" class="headerlink" title="1.1.4 关系模型"></a>1.1.4 关系模型</h4><ul><li><p>关系模型使用二维表来表示实体及其联系:行、列</p><ul><li>用表格表示实体集,用列表示属性,表结构表示实体的型</li><li>用表间的特定冗余信息表示实体间的联系(主键、外键)</li><li>行、列无序</li><li>列不可再分</li></ul></li><li><p>关系模型中的概念</p><img src="https://picbed.cloudchewie.com/blog/post/Introduction-to-Database-System-1/Chp1-%E5%85%B3%E7%B3%BB%E6%A8%A1%E5%9E%8B%E4%B8%AD%E7%9A%84%E6%A6%82%E5%BF%B5.png!blogimg" class="" width="400" title="关系模型中的概念"><ul><li>关系:即通常说的表,用于表示实体以及实体间的联系</li><li>元组:表中的一行即为一个元组</li><li>属性:表中的一列即为一个属性</li><li>主码(key):图中的某个属性组,它可以唯一确定一个元组</li><li>域(domain):属性的取值范围</li><li>分量:元组中的一个属性值</li><li>关系模式:对关系的描述,一般表示为:关系名(属性1,属性2,…,属性n)</li></ul></li></ul><h3 id="1-2-数据库系统"><a href="#1-2-数据库系统" class="headerlink" title="1.2 数据库系统"></a>1.2 数据库系统</h3><ul><li>数据库系统:由应用程序、DBMS、操作系统、硬件、人员(数据库管理员DBA)组成</li><li>数据库管理系统DBMS<ul><li>DB定义</li><li>操纵语言及编译程序</li><li>DB运行控制程序</li><li>实用程序</li></ul></li></ul><h2 id="二、关系数据库"><a href="#二、关系数据库" class="headerlink" title="二、关系数据库"></a>二、关系数据库</h2><h3 id="2-1-关系数据结构"><a href="#2-1-关系数据结构" class="headerlink" title="2.1 关系数据结构"></a>2.1 关系数据结构</h3><ul><li>域:一组值的集合,这组值具有相同的数据类型</li><li>笛卡尔积<ul><li>一组域$D_1,D_2,\cdots,D_n$的笛卡尔积为:$D_1\times D_2\times\cdots\times D_n = {(d_1 , d_2 , \cdots, d_n)\vert d_i∈D_i , i=1,\cdots,n}$</li><li>笛卡尔积的每个元素$(d_1,d_2,\cdots,d_n)$称作一个$n$元组</li><li>元组的每个值$d_i$称为一个分量</li><li>若$D_i$的基数为$m_i$,则笛卡尔积的基数为$$\prod\limits_{i=1}^{n}m_i$$</li></ul></li><li>关系<ul><li>笛卡尔积$D_1\times D_2\times\cdots\times D_n$的子集叫做在域$D_1,D_2,\cdots,D_n$上的关系,用$R(D_1 , D_2 ,\cdots, D_n )$表示</li><li>关系是笛卡尔积中有意义的子集</li></ul></li><li>候选码<ul><li>关系中的一个属性组,其值能唯一标识一个元组。</li><li>若从该属性组中去掉任何一个属性,它就不具有这一性质</li></ul></li><li>主属性:任何一个候选码中的属性称作主属性</li><li>主码:从一个关系的多个候选码中选定一个作为主码</li><li>外部码:关系R中的一个属性组,它不是R的码,但它与另一个关系S的码相对应,则称这个属性组为R的外部码</li><li>关系模式<ul><li>关系的描述称作关系模式,包括关系名、关系中的属性名、属性向域的映象、属性间的数据依赖关系等</li><li>关系模式是一个5元组$R(U,D,Dom,F)$,其中R是关系名;U是关系的属性集合;D是U中属性所来自的域的集合;DOM是属性向域映射的集合;F是属性间的依赖关系集合</li></ul></li><li>关系数据库<ul><li>在一个应用领域内,用关系表示实体及其联系,关系的集合构成一个关系数据库</li><li>其型是关系模式的集合,即数据库描述,称作数据库的<strong>内涵</strong>(Intension)</li><li>其值是某一时刻关系的集合,称作数据库的<strong>外延</strong>(Extension)</li></ul></li></ul><h3 id="2-2-关系的完整性"><a href="#2-2-关系的完整性" class="headerlink" title="2.2 关系的完整性"></a>2.2 关系的完整性</h3><ul><li>实体完整性:若属性A是基本关系R的主属性,则A不能取空值</li><li>参照完整性:如果关系$R_2$的外部码$F_k$与关系$R_1$的主码$P_k$相对应,则$R_2$中的每一个元组的$F_k$值或者等于$R_1$中某个元组的$P_k$值,或者为空值</li><li>用户定义的完整性:用户针对具体的应用环境定义的完整性约束条件</li></ul><h3 id="2-3-关系代数"><a href="#2-3-关系代数" class="headerlink" title="2.3 关系代数"></a>2.3 关系代数</h3><ul><li><p>关系代数是一种抽象的查询语言,通过对关系的运算来表达查询操作</p></li><li><p>基本概念</p><ul><li>如果两个关系的属性数目相同,且各属性的域相同,则两个关系是相容的</li><li>元组的连串:若$r=(r_1,r_2,\cdots,r_n)$,$s=(s_1,s_2,\cdots,s_n)$则定义二者的连串$\widehat{AB}=(r_1,r_2,\cdots,r_n,s_1,s_2,\cdots,s_n)$</li><li>给定关系模式$R(A_1 , A_2 ,\cdots, A_n)$,设$R$是它的一个具体的关系,$t\in R$是关系的一个元组<ul><li>$A={A_{i1},A_{i2},\cdots,A_{ik}}\subseteq {A_a,A_2,\cdots,A_n}$是<strong>属性列</strong>,$\overline{A}$表示${A_1,A_2,\cdots,A_n}$去掉$A$后剩余的属性组</li><li>$t[A_i]$表示元组$t$中相应于属性$A_i$的一个<strong>分量</strong></li><li>$t[A]={t[A_{i1}],t[A_{i2}],\cdots,t[A_{ik}]}$表示元组$t$在属性列$A$上各分量的集合</li><li>给定关系$R(X,Z)$,$X$和$Z$为属性组。当$t[X]=x$时,$x$在$R$中的<strong>象集</strong>为:$Z_X={t[Z]\vert t∈ R,t[X]=x}$ ,它表示$R$中属性组$X$上值为$x$的诸元组在$Z$上分量的集合</li></ul></li></ul></li><li><p>集合运算符(进行集合运算的两个关系必须相容):并$\cup$、交$\cap$、差$-$</p></li><li><p>关系运算符</p><ul><li><p>笛卡尔积$\times$</p><ul><li>设两个关系R,S,其度分别为n,m,则它们的广义笛卡尔积为$R\times S={\widehat{rs}\vert r\in R\land s\in S}$</li></ul></li><li><p>选择$\sigma$</p><ul><li>$\sigma_F(R)={t\vert t\in R\land F(t)=true}$</li><li>即$\sigma$运算的结果为满足条件F的元组集合</li></ul></li><li><p>投影$\Pi$</p><ul><li>$\Pi_A(R)={t[A]\vert t\in R}$,其中$A$为$R$的属性列</li><li>投影结果中要去掉相同的行</li></ul></li><li><p>连接$\Join$</p><ul><li><p>$R\underset{A\Theta B}\Join S={\widehat{rs}|r\in R\land s\in S\land r[A]\Theta S[B]}$</p></li><li><p>$R\underset{A\Theta B}\Join S=\sigma_{R[A]\Theta S[B]}(R\times S)$</p></li><li><p>即从两个关系的笛卡尔积中选取给定属性间满足一定条件的元组</p></li><li><p>等值连接:从两个关系的广义笛卡儿积中选取满足某一等值条件的元组,两个关系<strong>可以没有相同属性列</strong></p></li><li><p>自然连接:<strong>特殊的等值连接</strong>,从两个关系的广义笛卡儿积中选取在<strong>相同属性列</strong>上取值相等的元组,并<strong>去掉重复的列</strong></p></li></ul></li><li><p>除$÷$</p><ul><li>设有关系$R(X,Y)$和$S(Y,Z)$,则$R÷Z$可根据如下过程得到</li><li>在关系$S$中对$Y$做投影,得到$\Pi_Y(Z)$</li><li>关系$R$在属性列$X$上作取消重复值的投影,得到$\Pi_X(R)$</li><li>判断$\Pi_X(R)$中$x_i$的象集$Y_i$是否包含$\Pi_Y(Z)$,如果包含,则最终结果中包含$x_i$</li><li>即$R÷Z$表示【$R$在$X$上分量值$x$的象集$Y_x$包含$S$在$Y$上投影的集合】在$X$属性列上的投影</li></ul></li></ul></li><li><p>比较运算符:<、≤、>、≥、≠、=</p></li><li><p>逻辑运算符:与$\land$、或$\lor$、非$\lnot$</p></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 数据库系统 </tag>
</tags>
</entry>
<entry>
<title>数据库系统概论笔记(二)</title>
<link href="/posts/2023/02/22/notes/Introduction-to-Database-System-2/"/>
<url>/posts/2023/02/22/notes/Introduction-to-Database-System-2/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Introduction-to-Database-Systems.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">数据库系统概论笔记</div> <div class="tag-link-sitename"> 点击下载数据库系统概论笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="三、SQL语言"><a href="#三、SQL语言" class="headerlink" title="三、SQL语言"></a>三、SQL语言</h2><h3 id="3-1-数据库、表、索引、完整性约束"><a href="#3-1-数据库、表、索引、完整性约束" class="headerlink" title="3.1 数据库、表、索引、完整性约束"></a>3.1 数据库、表、索引、完整性约束</h3><ul><li><p>数据库、表、索引的创建、修改、删除</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">#创建数据库</span><br><span class="line"><span class="keyword">CREATE</span> DATABASE <span class="operator"><</span>数据库名<span class="operator">></span></span><br><span class="line">#删除数据库</span><br><span class="line"><span class="keyword">DROP</span> DATABASE <span class="operator"><</span>数据库名<span class="operator">></span></span><br><span class="line">#指定数据库</span><br><span class="line">DATABASE <span class="operator"><</span>数据库名<span class="operator">></span></span><br><span class="line">USE <span class="operator"><</span>数据库名<span class="operator">></span></span><br><span class="line">#创建表</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> [IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span>] <span class="operator"><</span>数据表名<span class="operator">></span></span><br><span class="line">(</span><br><span class="line"> <span class="operator"><</span>列定义列表<span class="operator">></span></span><br><span class="line"> <span class="operator"><</span>表约束列表<span class="operator">></span></span><br><span class="line">)</span><br><span class="line">#修改表</span><br><span class="line"><span class="keyword">ALTER</span> <span class="keyword">TABLE</span> <span class="operator"><</span>数据表名<span class="operator">></span> <span class="operator"><</span>修改子句列表<span class="operator">></span></span><br><span class="line">#删除表</span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">TABLE</span> <span class="operator"><</span>表名<span class="operator">></span></span><br><span class="line">#创建索引</span><br><span class="line"><span class="keyword">CREATE</span> [<span class="keyword">UNIQUE</span>] [CLUSTER] INDEX <span class="operator"><</span>索引名<span class="operator">></span> <span class="keyword">ON</span> <span class="operator"><</span>表名<span class="operator">></span> (<span class="operator"><</span>列名列表:<span class="operator"><</span>列名<span class="operator">></span> [<span class="keyword">ASC</span><span class="operator">|</span><span class="keyword">DESC</span>]<span class="operator">></span>)</span><br><span class="line">#删除索引</span><br><span class="line"><span class="keyword">DROP</span> INDEX <span class="operator"><</span>索引名<span class="operator">></span></span><br></pre></td></tr></table></figure></li><li><p>表的列定义</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="operator"><</span>列名<span class="operator">></span> <span class="operator"><</span>数据类型<span class="operator">></span> [列级约束列表<span class="operator">|</span><span class="keyword">NOT</span> <span class="keyword">NULL</span><span class="operator">|</span>AUTO_INCREMENT<span class="operator">|</span><span class="keyword">DEFAULT</span> <span class="operator"><</span>默认值<span class="operator">></span>]</span><br></pre></td></tr></table></figure></li><li><p>表的约束</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">#主码约束</span><br><span class="line">#单属性主码:列级约束方式</span><br><span class="line"><span class="operator"><</span>列名<span class="operator">></span> <span class="operator"><</span>数据类型<span class="operator">></span> <span class="keyword">PRIMARY</span> KEY</span><br><span class="line">#单属性主码:表级约束方式</span><br><span class="line">[CONSTRANIT [约束名]] <span class="keyword">PRIMARY</span> KEY (<span class="operator"><</span>列名<span class="operator">></span>)</span><br><span class="line">#多属性主码:表级约束方式</span><br><span class="line">[CONSTRANIT [约束名]] <span class="keyword">PRIMARY</span> KEY (<span class="operator"><</span>列名列表<span class="operator">></span>)</span><br><span class="line">#唯一性约束</span><br><span class="line">#列级约束方式</span><br><span class="line"><span class="operator"><</span>列名<span class="operator">></span> <span class="operator"><</span>数据类型<span class="operator">></span> <span class="keyword">UNIQUE</span></span><br><span class="line">#表级约束方式</span><br><span class="line">[CONSTRANIT [约束名]] <span class="keyword">UNIQUE</span> (<span class="operator"><</span>列名列表<span class="operator">></span>)</span><br><span class="line">#外码约束</span><br><span class="line">#列级约束方式</span><br><span class="line"><span class="operator"><</span>列名<span class="operator">></span> <span class="operator"><</span>数据类型<span class="operator">></span> <span class="keyword">REFERENCES</span> <span class="operator"><</span>表名<span class="operator">></span>(<span class="operator"><</span>列名<span class="operator">></span>)</span><br><span class="line">#表级约束方式</span><br><span class="line">[CONSTRANIT [约束名]] <span class="keyword">FOREIGN</span> KEY (<span class="operator"><</span>列名列表<span class="operator">></span>) <span class="keyword">REFERENCES</span> <span class="operator"><</span>表名<span class="operator">></span>(<span class="operator"><</span>列名列表<span class="operator">></span>)</span><br><span class="line">#检查约束</span><br><span class="line">#列级约束方式</span><br><span class="line"><span class="operator"><</span>列名<span class="operator">></span> <span class="operator"><</span>数据类型<span class="operator">></span> <span class="keyword">CHECK</span> (<span class="operator"><</span>检查条件<span class="operator">></span>)</span><br><span class="line">#表级约束方式</span><br><span class="line">[CONSTRANIT [约束名]] <span class="keyword">CHECK</span> (<span class="operator"><</span>检查条件<span class="operator">></span>)</span><br></pre></td></tr></table></figure></li><li><p>表的修改子句</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">#添加约束</span><br><span class="line"><span class="keyword">ADD</span> <span class="operator"><</span>表级约束子句<span class="operator">></span></span><br><span class="line">#删除约束</span><br><span class="line"><span class="keyword">DROP</span> {<span class="keyword">CHECK</span><span class="operator">|</span><span class="keyword">CONSTRAINT</span><span class="operator">|</span><span class="keyword">FOREIGN</span> KEY} <span class="operator"><</span>约束名<span class="operator">></span></span><br><span class="line">#删除主码</span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">PRIMARY</span> KEY</span><br><span class="line">▲注:等同于语句<span class="keyword">DROP</span> INDEX "PRIMARY" <span class="keyword">on</span> <span class="operator"><</span>表名<span class="operator">></span></span><br><span class="line">#删除唯一性约束</span><br><span class="line">▲注:等同于语句<span class="keyword">DROP</span> INDEX <span class="operator"><</span>索引名<span class="operator">></span> <span class="keyword">on</span> <span class="operator"><</span>表名<span class="operator">></span></span><br></pre></td></tr></table></figure></li><li><p>断言</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">#创建断言,其中<span class="keyword">CHECK</span>子句类似于<span class="keyword">WHERE</span>子句</span><br><span class="line"><span class="keyword">CREATE</span> ASSERTION <span class="operator"><</span>断言名<span class="operator">></span> <span class="operator"><</span><span class="keyword">CHECK</span>子句<span class="operator">></span></span><br><span class="line">#删除断言</span><br><span class="line"><span class="keyword">DROP</span> ASSERTION <span class="operator"><</span>断言名<span class="operator">></span></span><br></pre></td></tr></table></figure></li></ul><h3 id="3-2-数据查询"><a href="#3-2-数据查询" class="headerlink" title="3.2 数据查询"></a>3.2 数据查询</h3><ul><li><p>查询子句的基本结构</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="operator"><</span>列组合<span class="operator">></span> <span class="keyword">FROM</span> <span class="operator"><</span>数据表列表<span class="operator">></span></span><br><span class="line">[<span class="keyword">WHERE</span> <span class="operator"><</span>行条件子句<span class="operator">></span>]</span><br><span class="line">[<span class="keyword">GROUP</span> <span class="keyword">BY</span> <span class="operator"><</span>分组子句<span class="operator">></span>]</span><br><span class="line">[<span class="keyword">HAVING</span> <span class="operator"><</span>组条件子句<span class="operator">></span>]</span><br><span class="line">[<span class="keyword">ORDER</span> <span class="keyword">BY</span> <span class="operator"><</span>排序子句<span class="operator">></span>]</span><br></pre></td></tr></table></figure><ul><li>根据WHERE子句的检索条件,从FROM子句指定的基本表或视图中选取满足条件的元组,再按照SELECT子句中指定的列,投影得到结果表</li><li>如果有GROUP子句,则将查询结果按照<列名1>相同的值进行分组</li><li>如果GROUP子句后有HAVING短语,则只输出满足HAVING条件的元组</li><li>如果有ORDER子句,查询结果还要按照<列名2>的值进行排序</li></ul></li><li><p>SELECT子句</p><ul><li><p>目标列形式</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">#全部列</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">from</span> SC</span><br><span class="line">#列名</span><br><span class="line"><span class="keyword">SELECT</span> Sno,Score <span class="keyword">from</span> SC</span><br><span class="line">#算术表达式,可包含<span class="operator">+</span>、<span class="operator">-</span>、<span class="operator">*</span>、<span class="operator">/</span>、百分数</span><br><span class="line"><span class="keyword">SELECT</span> Sno,Score<span class="operator">*</span><span class="number">1.2</span> <span class="keyword">from</span> SC</span><br><span class="line">#聚集函数:SUM、AVG</span><br><span class="line"><span class="keyword">SELECT</span> <span class="built_in">SUM</span>(Score) <span class="keyword">from</span> SC</span><br></pre></td></tr></table></figure></li><li><p>重复元组的处理</p><ul><li><p>缺省或关键字ALL表示保留重复元组</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> [<span class="keyword">ALL</span>] <span class="operator"><</span>列组合<span class="operator">></span> <span class="keyword">FROM</span> <span class="operator"><</span>数据表列表<span class="operator">></span></span><br></pre></td></tr></table></figure></li><li><p>关键字DISTINCT表示去除重复元组</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> <span class="keyword">DISTINCT</span> <span class="operator"><</span>列组合<span class="operator">></span> <span class="keyword">FROM</span> <span class="operator"><</span>数据表列表<span class="operator">></span></span><br></pre></td></tr></table></figure></li></ul></li><li><p>列的重命名</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#旧目标列可以是列名、表达式、聚集函数名</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator"><</span>旧目标列<span class="operator">></span> <span class="keyword">AS</span> <span class="operator"><</span>新列名<span class="operator">></span> <span class="keyword">FROM</span> <span class="operator"><</span>数据表列表<span class="operator">></span></span><br></pre></td></tr></table></figure></li></ul></li><li><p>FROM子句</p><ul><li><p>限定:同时指定一个或多个表(或视图)时,如果选择列表中存在同名列,这时应使用对象名限定这些列</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> username,city.cityid <span class="keyword">FROM</span> <span class="keyword">user</span>,city <span class="keyword">WHERE</span> user.cityid<span class="operator">=</span>city.cityid</span><br></pre></td></tr></table></figure></li><li><p>表的重命名</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> A.cityid <span class="keyword">FROM</span> <span class="keyword">user</span> <span class="keyword">AS</span> A</span><br></pre></td></tr></table></figure></li><li><p>子查询</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> CS.name <span class="keyword">FROM</span> (<span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> S <span class="keyword">WHERE</span> dept<span class="operator">=</span>"CS") <span class="keyword">AS</span> CS <span class="keyword">WHERE</span> CS.age<span class="operator">></span><span class="number">20</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p>WHERE子句</p><ul><li><p>比较运算符</p></li><li><p>逻辑运算符:AND、OR、NOT</p></li><li><p>BETWEEN条件</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> Sname <span class="keyword">FROM</span> SC <span class="keyword">WHERE</span> Score <span class="keyword">BETWEEN</span> <span class="number">80</span> <span class="keyword">AND</span> <span class="number">100</span></span><br></pre></td></tr></table></figure></li><li><p>模糊查询</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">#<span class="operator">%</span>匹配零个以上字符、_匹配单个字符、\转义字符</span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> SC <span class="keyword">WHERE</span> Sname <span class="keyword">LIKE</span> <span class="string">'张%'</span></span><br><span class="line"><span class="keyword">SELECT</span> <span class="operator">*</span> <span class="keyword">FROM</span> SC <span class="keyword">WHERE</span> Sname <span class="keyword">LIKE</span> <span class="string">'张\_%'</span></span><br></pre></td></tr></table></figure></li></ul></li><li><p>ORDER子句</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SELECT <列组合> FROM <数据表列表> ORDER BY <列名列表:<列名> [ASC|DESC]></span><br></pre></td></tr></table></figure></li><li><p>GROUP与HAVING子句</p><ul><li>GROUP BY:将表中的元组按指定列上值相等的原则分组,然后在每一分组上使用聚集函数,得到单一值</li><li>HAVING:对分组进行选择,可以针对聚集函数的结果值进行筛选,作用于分组计算的结果集</li></ul></li><li><p>空值</p><ul><li>如果null参与算术运算,则该算术表达式的值为null</li><li>如果null参与比较运算,则结果可视为false</li><li>如果null参与聚集运算,则除count(*)之外其它聚集函数都忽略null</li></ul><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> Sname <span class="keyword">FROM</span> SC <span class="keyword">WHERE</span> Score <span class="keyword">IS</span> <span class="keyword">NULL</span></span><br><span class="line"><span class="keyword">SELECT</span> Sname <span class="keyword">FROM</span> SC <span class="keyword">WHERE</span> Score <span class="keyword">IS</span> <span class="keyword">NOT</span> <span class="keyword">NULL</span></span><br></pre></td></tr></table></figure></li><li><p>连接查询</p><ul><li>等值连接:某两张表在某个属性上相等</li><li>自然连接:在等值连接中去掉重复的属性列</li><li>自身连接:一个表与其自己进行连接,同一个数据表取不同别名</li><li>外连接:将悬浮元组包含在连接结果中的连接</li></ul></li><li><p>嵌套查询</p><ul><li><p>子查询是嵌套在另一查询中的 Select-From-Where 表达式</p></li><li><p>子查询中不能使用 Order By 子句,Order By子句只能对最终查询结果进行排序</p></li><li><p>集合成员资格</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#判断表达式的值是否在子查询的结果中</span><br><span class="line"><span class="operator"><</span>表达式<span class="operator">></span> [<span class="keyword">NOT</span>] <span class="keyword">IN</span> <span class="operator"><</span>子查询<span class="operator">></span></span><br></pre></td></tr></table></figure></li><li><p>集合之间的比较</p><ul><li><p>当用户能确切知道内层查询返回的是单值时,可以用>、<、=、>=、<=、!=或<>等比较运算符</p></li><li><p>ANY:表达式的值至少与子查询结果中的一个值相比满足</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> Sname <span class="keyword">FROM</span> Student <span class="keyword">WHERE</span> Sage <span class="operator"><</span> <span class="keyword">ANY</span> (<span class="keyword">SELECT</span> Sage <span class="keyword">FROM</span> Student <span class="keyword">WHERE</span> Sdept=’<span class="keyword">IS</span>’)</span><br></pre></td></tr></table></figure></li><li><p>ALL:表达式的值与子查询结果中的所有的值相比都满足</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">SELECT</span> Sname <span class="keyword">FROM</span> Student <span class="keyword">WHERE</span> Sage <span class="operator"><</span> <span class="keyword">ALL</span> (<span class="keyword">SELECT</span> Sage <span class="keyword">FROM</span> Student <span class="keyword">WHERE</span> Sdept=’<span class="keyword">IS</span>’)</span><br></pre></td></tr></table></figure></li></ul></li><li><p>相关嵌套查询</p><ul><li>Exists:测试该子查询的结果是否有元组,带有Exists的子查询不返回任何数据,只产生True/False</li></ul></li></ul></li><li><p>集合查询</p><ul><li>集合并:union</li><li>集合交:intersect</li><li>集合差:minus</li><li>集合操作自动去除重复元组,如果要保留重复元组,必须用all关键词指明</li></ul></li></ul><h3 id="3-3-数据的增删改"><a href="#3-3-数据的增删改" class="headerlink" title="3.3 数据的增删改"></a>3.3 数据的增删改</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#插入单个元组</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> <span class="operator"><</span>表名<span class="operator">></span> [(<span class="operator"><</span>列列表<span class="operator">></span>)] <span class="keyword">VALUES</span>(<span class="operator"><</span>值列表<span class="operator">></span>)</span><br><span class="line">#插入子查询结果(要求子查询结果的模式和要插入的模式相同)</span><br><span class="line"><span class="keyword">INSERT</span> <span class="keyword">INTO</span> <span class="operator"><</span>表名<span class="operator">></span> [(<span class="operator"><</span>列列表<span class="operator">></span>)] (<span class="operator"><</span>子查询结果集<span class="operator">></span>)</span><br><span class="line">#删除元组(没有<span class="keyword">WHERE</span>语句时,删除所有元组)</span><br><span class="line"><span class="keyword">DELETE</span> <span class="keyword">FROM</span> <span class="operator"><</span>表名<span class="operator">></span> [<span class="keyword">WHERE</span> <span class="operator"><</span>条件表达式<span class="operator">></span>]</span><br><span class="line">#更新元组</span><br><span class="line"><span class="keyword">UPDATE</span> <span class="operator"><</span>表名<span class="operator">></span> <span class="keyword">SET</span> <span class="operator"><</span>列修改列表:<span class="operator"><</span>列名<span class="operator">>=</span>表达式<span class="operator">|</span>子查询<span class="operator">></span> [<span class="keyword">WHERE</span> <span class="operator"><</span>条件表达式<span class="operator">></span>]</span><br><span class="line">#跨表更新</span><br><span class="line"><span class="keyword">UPDATE</span> <span class="operator"><</span>表名<span class="operator">></span> <span class="keyword">SET</span> <span class="operator"><</span>列修改列表:<span class="operator"><</span>列名<span class="operator">>=</span>表达式<span class="operator">|</span>子查询<span class="operator">></span> [<span class="keyword">FROM</span> <span class="operator"><</span>表列表<span class="operator">></span>][<span class="keyword">WHERE</span> <span class="operator"><</span>条件表达式<span class="operator">></span>]</span><br></pre></td></tr></table></figure><h3 id="3-4-视图"><a href="#3-4-视图" class="headerlink" title="3.4 视图"></a>3.4 视图</h3><ul><li><p>视图是从一个或几个基本表(或视图)导出的一个虚表</p></li><li><p>数据库中只存放视图的定义而不存放视图的数据</p></li><li><p>当基表中的数据发生变化时从视图中查出的数据也随之改变</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">#创建视图</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">VIEW</span> <span class="operator"><</span>视图名<span class="operator">></span> <span class="keyword">AS</span> <span class="operator"><</span>查询表达式<span class="operator">></span> [<span class="keyword">WITH</span> <span class="keyword">CHECK</span> OPTION]</span><br><span class="line">#删除视图</span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">VIEW</span> <span class="operator"><</span>视图名<span class="operator">></span></span><br></pre></td></tr></table></figure></li><li><p>视图的属性名缺省为子查询结果中的属性名,也可以显式指明</p></li><li><p>当对视图进行insert,update时,要检查是否满足with check option条件</p></li><li><p>视图更新</p><ul><li>对视图的更新,最终要转换为<strong>对基表的更新</strong></li><li>SELECT子句中的目标列不能包含聚集函数</li><li>SELECT子句中不能使用UNIQUE或DISTINCT关键字</li><li>不能包括GROUP BY子句</li><li>不能包括经算术表达式计算出来的列</li><li>对于行列子集视图可以更新(视图是从单个基本表使用选择、投影操作导出的,并且包含了基本表的主码)</li></ul></li></ul><h3 id="3-5-触发器、存储过程、用户自定义函数"><a href="#3-5-触发器、存储过程、用户自定义函数" class="headerlink" title="3.5 触发器、存储过程、用户自定义函数"></a>3.5 触发器、存储过程、用户自定义函数</h3><ul><li><p>触发器</p><ul><li><p>触发器是与某个表绑定的命名存储对象,由一组SQL语句组成</p></li><li><p>当该表发生某个操作时,触发器将会被触发执行,一般用于维护数据库<strong>完整性规则</strong></p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">#创建触发器</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TRIGGER</span> <span class="operator"><</span>触发器名<span class="operator">></span></span><br><span class="line">{BEFORE<span class="operator">|</span>AFTER}</span><br><span class="line">{<span class="operator"><</span>触发事件列表,以<span class="keyword">OR</span>连接<span class="operator">></span>}</span><br><span class="line"><span class="keyword">ON</span> <span class="operator"><</span>数据表名<span class="operator">></span></span><br><span class="line">[<span class="keyword">FROM</span> <span class="operator"><</span>引用数据表名<span class="operator">></span>]</span><br><span class="line">[<span class="keyword">FOR</span> [<span class="keyword">EACH</span>] {<span class="type">ROW</span><span class="operator">|</span>STATEMENT}]</span><br><span class="line">[<span class="keyword">WHEN</span> (<span class="operator"><</span>条件<span class="operator">></span>)]</span><br><span class="line"><span class="keyword">EXECUTE</span> <span class="keyword">PROCEDURE</span> <span class="operator"><</span>函数名<span class="operator">></span>(<span class="operator"><</span>参数列表<span class="operator">></span>)</span><br><span class="line">#删除触发器</span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">TRIGGER</span> <span class="operator"><</span>触发器名<span class="operator">></span> <span class="keyword">ON</span> <span class="operator"><</span>表名<span class="operator">></span></span><br></pre></td></tr></table></figure></li><li><p>行级触发器:数据每发生一次操作即执行一次触发器动作,如UPDATE多行的某个字段时</p></li><li><p>语句级触发器(默认):触发事件语句执行完后才执行一次触发器动作</p></li><li><p>触发器的激活</p><ul><li>执行该表上的BEFORE触发器</li><li>执行激活触发器的SQL语句</li><li>执行该表上的AFTER触发器</li></ul></li></ul></li><li><p>游标</p><ul><li><p>SQL操作都是面向集合的,即操作的对象以及运算的结果均为集合</p></li><li><p>游标(CURSOR)相当于一个存储于内存的带有指针的表,每次可以存取指针指向的一行数据,并将指针向前推进一行</p></li><li><p>使用游标可以遍历某个查询语句的结果集</p></li><li><p>游标不可滚动,即只能依次遍历,不能反向遍历,不能跳跃遍历,不能随机访问,不能修改游标中的数据</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">#定义变量</span><br><span class="line"><span class="keyword">DECLARE</span> <span class="operator"><</span>变量名<span class="operator">></span> <span class="operator"><</span>变量数据类型<span class="operator">></span> [<span class="keyword">DEFAULT</span> <span class="operator"><</span>默认值<span class="operator">></span>]</span><br><span class="line">#定义游标,游标的数据集将为查询语句的结果</span><br><span class="line"><span class="keyword">CURSOR</span> <span class="operator"><</span>游标名<span class="operator">></span> <span class="keyword">FOR</span> <span class="operator"><</span>查询语句<span class="operator">></span></span><br><span class="line">#打开定义过的游标,并初始化指针</span><br><span class="line"><span class="keyword">OPEN</span> <span class="operator"><</span>游标名<span class="operator">></span></span><br><span class="line">#读取游标,将读取到的一行数据写入变量列表中</span><br><span class="line">#读取未打开的游标会出错</span><br><span class="line"><span class="keyword">FETCH</span> [[NEXT] <span class="keyword">FROM</span>] <span class="operator"><</span>游标名<span class="operator">></span> <span class="keyword">INTO</span> <span class="operator"><</span>变量列表<span class="operator">></span></span><br><span class="line">#关闭游标,关闭未打开的游标会出错</span><br><span class="line"><span class="keyword">CLOSE</span> <span class="operator"><</span>游标名<span class="operator">></span></span><br></pre></td></tr></table></figure></li></ul></li><li><p>存储过程</p><ul><li><p>存储过程是一个SQL语句组合</p></li><li><p>在创建时进行预编译,首次被调用时进行解析,以后再被调用,则可直接执行</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">#创建存储过程</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">PROCEDURE</span> <span class="operator"><</span>存储过程名称<span class="operator">></span></span><br><span class="line">([@<span class="operator"><</span>参数名称<span class="operator">></span> <span class="operator"><</span>参数数据类型<span class="operator">></span> [<span class="type">VARYING</span>][<span class="operator">=</span><span class="operator"><</span>默认值<span class="operator">></span>][<span class="keyword">OUT</span><span class="operator">|</span>OUTPUT]])</span><br><span class="line"><span class="keyword">AS</span></span><br><span class="line">[定义变量列表]</span><br><span class="line"><span class="keyword">BEGIN</span></span><br><span class="line"><span class="operator"><</span><span class="keyword">SQL</span>语句组合<span class="operator">></span></span><br><span class="line"><span class="keyword">END</span></span><br><span class="line">#执行存储过程</span><br><span class="line">[<span class="keyword">EXECUTE</span><span class="operator">|</span><span class="keyword">EXEC</span>] <span class="operator"><</span>存储过程名称<span class="operator">></span> <span class="operator"><</span>参数列表<span class="operator">></span></span><br><span class="line">#删除存储过程</span><br><span class="line"><span class="keyword">DROP</span> <span class="keyword">PROCEDURE</span> <span class="operator"><</span>存储过程名称<span class="operator">></span></span><br></pre></td></tr></table></figure></li><li><p>数据传递方式</p><ul><li>输入参数</li><li>输出参数使用OUTPUT标识</li><li>RETURN语句返回单个int型数据,如操作过程中受影响的行数,错误码</li><li>RETURN不能返回NULL,若试图返回NULL,将生成警告信息并返回0</li></ul></li></ul></li><li><p>用户自定义函数</p><ul><li><p>自定义函数可以像数据库内部函数一样在SQL语句中使用,如WHERE子句、SELECT子句、表达式中</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">FUNCTION</span> <span class="operator"><</span>函数名<span class="operator">></span></span><br><span class="line">([参数列表:<span class="operator"><</span>参数名称<span class="operator">></span> <span class="operator"><</span>参数数据类型<span class="operator">></span>])</span><br><span class="line"><span class="keyword">RETURNS</span> <span class="operator"><</span>返回数据类型<span class="operator">></span></span><br><span class="line"><span class="keyword">BEGIN</span></span><br><span class="line"><span class="operator"><</span>函数体<span class="operator">></span></span><br><span class="line"><span class="keyword">RETURN</span> <span class="operator"><</span>返回值:常量<span class="operator">/</span>表达式<span class="operator">/</span>语句查询结果<span class="operator">></span></span><br><span class="line"><span class="keyword">END</span></span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="3-6-安全性控制"><a href="#3-6-安全性控制" class="headerlink" title="3.6 安全性控制"></a>3.6 安全性控制</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">#创建用户</span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">USER</span> <span class="operator"><</span>用户名<span class="operator">></span> <span class="keyword">with</span> PASSWORD <span class="operator"><</span>密码<span class="operator">></span></span><br><span class="line">#创建角色,角色是权限的集合</span><br><span class="line"><span class="keyword">CREATE</span> ROLE [IF <span class="keyword">NOT</span> <span class="keyword">EXISTS</span>] <span class="operator"><</span>角色列表<span class="operator">></span></span><br><span class="line">#删除角色,拥有该角色的用户失去该角色定义的权限集合</span><br><span class="line"><span class="keyword">DROP</span> ROLE [IF <span class="keyword">EXISTS</span>] <span class="operator"><</span>角色列表<span class="operator">></span></span><br><span class="line">#授权,其中<span class="keyword">WITH</span> <span class="keyword">GRANT</span> OPTION表示获得权限的用户可以把权限授予他人</span><br><span class="line"><span class="keyword">GRANT</span> <span class="operator"><</span>角色名<span class="operator">></span> <span class="keyword">TO</span> <span class="operator"><</span>用户列表<span class="operator">></span> [<span class="keyword">WITH</span> <span class="keyword">GRANT</span> OPTION]</span><br><span class="line"><span class="keyword">GRANT</span> <span class="operator"><</span>权限列表<span class="operator">></span> [<span class="keyword">ON</span> <span class="operator"><</span>对象类型<span class="operator">></span> <span class="operator"><</span>对象名<span class="operator">></span>] <span class="keyword">TO</span> <span class="operator"><</span>用户列表<span class="operator">></span> [<span class="keyword">WITH</span> <span class="keyword">GRANT</span> OPTION]</span><br><span class="line">#收回,若用户已将权限授予其它用户,则也一并收回</span><br><span class="line"><span class="keyword">REVOKE</span> <span class="operator"><</span>权限列表<span class="operator">></span> [<span class="keyword">ON</span> <span class="operator"><</span>对象类型<span class="operator">></span> <span class="operator"><</span>对象名<span class="operator">></span>] <span class="keyword">FROM</span> <span class="operator"><</span>用户列表<span class="operator">|</span>PUBLIC<span class="operator">></span></span><br><span class="line">#权限列表</span><br><span class="line"><span class="keyword">ALL</span> PRIVILIGES</span><br><span class="line">{<span class="keyword">SELECT</span><span class="operator">|</span><span class="keyword">UPDATE</span>} [(<span class="operator"><</span>字段列表<span class="operator">></span>)]</span><br><span class="line">{<span class="keyword">INSERT</span><span class="operator">|</span><span class="keyword">DELETE</span><span class="operator">|</span><span class="keyword">ALTER</span>}</span><br></pre></td></tr></table></figure><h3 id="3-7-并发控制"><a href="#3-7-并发控制" class="headerlink" title="3.7 并发控制"></a>3.7 并发控制</h3><h4 id="3-7-1-并发控制概述"><a href="#3-7-1-并发控制概述" class="headerlink" title="3.7.1 并发控制概述"></a>3.7.1 并发控制概述</h4><ul><li>并发控制的必要性<ul><li>事务串行执行:每个时刻只有一个事务运行,其他事务必须等到这个事务结束以后方能运行,不能充分利用系统资源,发挥数据库共享资源的特点</li><li>交叉并发方式:单处理机系统中,并行事务的并行操作轮流交叉运行</li><li>同时并发方式:多处理机系统中,可实现多个事务真正的并行运行</li></ul></li><li>并发控制带来的数据不一致性<ul><li>丢失修改<ul><li>两个事务T1和T2读入同一数据并修改,T2的提交结果破坏了T1提交的结果,导致T1的修改被丢失</li></ul></li><li>不可重复读<ul><li>事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时,得到与前一次不同的值</li><li>事务T1按一定条件从数据库中读取了某些数据记录后,事务T2删除了其中部分记录,当T1再次按相同条件读取数据时,发现某些记录消失了</li><li>事务T1按一定条件从数据库中读取某些数据记录后,事务T2插入了一些记录,当T1再次按相同条件读取数据时,发现多了一些记录。</li></ul></li><li>读脏数据<ul><li>事务T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤销,这时T1已修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,此时T2读到的数据就为“脏”数据</li></ul></li></ul></li></ul><h4 id="3-7-2-封锁"><a href="#3-7-2-封锁" class="headerlink" title="3.7.2 封锁"></a>3.7.2 封锁</h4><ul><li><p>封锁概述</p><ul><li><p>事务T在对某个数据对象操作之前,先向系统发出请求,对其加锁</p></li><li><p>排它锁(Exclusive Locks,简记为X锁、写锁)</p><ul><li>若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁</li><li>保证其他事务在T释放A上的锁之前<strong>不能再读取和修改</strong>A</li></ul></li><li><p>共享锁(Share Locks,简记为S锁、读锁)</p><ul><li>若事务T对数据对象A加上S锁,则其它事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁</li><li><strong>保证其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改</strong></li></ul></li><li><p>锁的相容性矩阵(Y表示相容,N表示不相容)</p></li></ul></li></ul><table><thead><tr><th align="center"></th><th align="center">X</th><th align="center">S</th><th align="center">-</th></tr></thead><tbody><tr><td align="center">X</td><td align="center">N</td><td align="center">N</td><td align="center">Y</td></tr><tr><td align="center">S</td><td align="center">N</td><td align="center">Y</td><td align="center">Y</td></tr><tr><td align="center">-</td><td align="center">Y</td><td align="center">Y</td><td align="center">Y</td></tr></tbody></table><ul><li><p>封锁解决数据不一致性</p><ul><li>解决丢失修改问题:修改前先对待修改数据对象加X锁</li><li>解决不可重复读问题:读前先对待读数据对象加S锁</li><li>解决读脏数据问题:T1修改前先对待修改数据对象加X锁,T2读前先对待读数据对象加S锁</li></ul></li><li><p>活锁</p><ul><li>T2、T3、…、Tn依次等待事务T1释放锁,而系统依次批准了T3、…、Tn的请求,T2将有可能永远等待,即活锁</li><li>采用<strong>先来先服务</strong>的策略避免活锁:当多个事务请求封锁同一数据对象时,按请求封锁的先后次序对这些事务排队,该数据对象上的锁一旦释放,首先批准申请队列中第一个事务获得锁</li></ul></li><li><p>死锁</p><ul><li>事务T1封锁了数据R1,而T2封锁了数据R2;T1又请求封锁R2,因T2已封锁了R2,于是T1等待T2释放R2上的锁;接着T2又申请封锁R1,因T1已封锁了R1,T2也只能等待T1释放R1上的锁,这样T1在等待T2,而T2又在等待T1,T1和T2两个事务永远不能结束,形成死锁</li><li>预防死锁<ul><li>一次封锁法:每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行</li><li>顺序封锁法:预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁</li><li>时间戳优先级法:较老时间戳具有更高优先级,当一事务重启时,它的新优先级仍为原先的时间戳</li></ul></li><li>死锁的检测与解除<ul><li>超时法检测:如果一个事务的等待时间超过了规定的时限,就认为发生了死锁;阈值过短则可能误判,过长则死锁不能及时发现</li><li>事务等待图法检测<ul><li>事务等待图是一个有向图G=(T,U),其中T为结点的集合,每个结点表示正运行的事务,U为边的集合,每条边表示事务等待的情况,若T1等待T2,则T1与T2之间有一条从T1指向T2的有向边</li><li>并发控制子系统周期性地(比如每隔数秒)生成事务等待图,检测事务。<strong>如果发现图中存在回路,则表示系统中出现了死锁</strong></li></ul></li></ul></li></ul></li><li><p>封锁协议</p><img src="https://picbed.cloudchewie.com/blog/post/Introduction-to-Database-System-2/Chp1-%E5%B0%81%E9%94%81%E5%8D%8F%E8%AE%AE.png!blogimg" class="" width="400" title="封锁协议"><ul><li>1级封锁协议<ul><li>事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放</li><li>1级封锁协议可防止丢失修改</li><li>在1级封锁协议中,如果是读数据,不需要加锁的,所以它不能保证可重复读和不读“脏”数据</li></ul></li><li>2级封锁协议<ul><li>1级封锁协议+事务T在读取数据R前必须先加S锁,读完后即可释放S锁</li><li>在2级封锁协议中,由于读完数据后即可释放S锁,所以它不能保证可重复读</li></ul></li><li>3级封锁协议<ul><li>1级封锁协议+事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放</li><li>3级封锁协议可防止丢失修改、读脏数据和不可重复读</li></ul></li></ul></li></ul><h4 id="3-7-3-可串行性"><a href="#3-7-3-可串行性" class="headerlink" title="3.7.3 可串行性"></a>3.7.3 可串行性</h4><ul><li>可串行化(Serializable)调度:多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行地执行这些事务时的结果相同</li><li>冲突可串行化调度<ul><li>冲突可串行化是可串行化调度的<strong>充分条件</strong></li><li>冲突操作指<strong>不同事务对同一数据的读写操作和写写操作</strong>,冲突操作中两个操作的次序发生改变将导致结果改变</li><li>某个调度在冲突操作的次序不改变的前提下,通过交换两个事务不冲突操作的次序,转变为可串行的调度,则该调度是冲突可串行化的</li></ul></li></ul><h4 id="3-7-4-两段锁协议"><a href="#3-7-4-两段锁协议" class="headerlink" title="3.7.4 两段锁协议"></a>3.7.4 两段锁协议</h4><ul><li>当一个事务满足以下条件时,是满足两段锁协议的<ul><li>在对任何数据进行读写操作前,首先申请并获得对该数据的封锁</li><li>在释放一个封锁后,事务不再申请和获得任何其他封锁</li><li>事务被划分为两个阶段<ul><li>扩展阶段:获得封锁且不释放任何锁</li><li>收缩阶段:释放封锁且不申请任何锁</li></ul></li></ul></li><li>若并发执行的所有事务均满足两段锁协议,则这些事务的任何调度策略都是可串行化的,这是可串行化的<strong>充分条件</strong></li><li>遵循两段锁协议的事务不要求事务必须一次将所有要使用的数据全部加锁,因此<strong>可能发生死锁</strong></li></ul><h3 id="3-8-数据库恢复技术"><a href="#3-8-数据库恢复技术" class="headerlink" title="3.8 数据库恢复技术"></a>3.8 数据库恢复技术</h3><h4 id="3-8-1-事务"><a href="#3-8-1-事务" class="headerlink" title="3.8.1 事务"></a>3.8.1 事务</h4><ul><li><p>事务概念</p><ul><li>事务(Transaction):用户定义的具有交易特性的一个数据库操作序列</li><li>特性<ul><li>原子性:事务是数据库的逻辑工作单位,事务中各操作要么都做,要么都不做</li><li>一致性:数据库状态与外部状态一致</li><li>隔离性:一个事务的执行不能被其他事务干扰</li><li>持续性:一个事务一旦提交,它对数据库中数据的改变就应该是永久性的</li></ul></li></ul></li><li><p>定义事务</p><ul><li><p>显式定义</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#提交事务</span><br><span class="line"><span class="keyword">BEGIN</span> TRANSACTION</span><br><span class="line"><span class="keyword">SQL</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">COMMIT</span></span><br><span class="line">#回滚事务</span><br><span class="line"><span class="keyword">BEGIN</span> TRANSACTION</span><br><span class="line"><span class="keyword">SQL</span></span><br><span class="line">...</span><br><span class="line"><span class="keyword">ROLLBACK</span></span><br></pre></td></tr></table></figure></li><li><p>隐式定义:DBMS按缺省规定自动划分事务</p></li></ul></li></ul><h4 id="3-8-2-数据库恢复"><a href="#3-8-2-数据库恢复" class="headerlink" title="3.8.2 数据库恢复"></a>3.8.2 数据库恢复</h4><ul><li>故障种类<ul><li>事务故障:某个事务在运行过程中由于种种原因未运行至预期的终点便结束</li><li>系统故障:操作系统或DBMS代码错误等</li><li>介质故障:磁盘损坏等原因</li></ul></li><li>恢复的实现:建立冗余数据<ul><li>数据转储<ul><li>静态转储:在系统中无运行事务时进行转储,转储期间不允许对数据库进行任何存取修改活动</li><li>动态转储:转储与事务并发执行,但获得一致性副本较麻烦,因此需要将动态转储期间各事务对数据库的修改活动登记下来,建立日志文件</li><li>海量转储:定期或不定期将数据库全部数据转储,其转储量大,易造成重复转储</li><li>增量转储:每次转储上次转储后更新过的数据,其备份量小,但恢复过程较复杂</li></ul></li><li>登录日志文件<ul><li>用来记录事务对数据库的更新操作的文件</li><li>以记录为单位的日志文件内容<ul><li>各个事务的开始标记(BEGIN TRANSACTION)</li><li>各个事务的结束标记(COMMIT或ROLLBACK)</li><li>各个事务的所有更新操作</li><li>与事务有关的内部更新操作</li><li>每条日志记录的内容<ul><li>事务标识</li><li>操作类型(插入、删除或修改)</li><li>操作对象(记录ID)</li><li>更新前数据的旧值(对插入操作而言,此项为空值)</li><li>更新后数据的新值(对删除操作而言, 此项为空值)</li></ul></li></ul></li><li>以数据块为单位的日志文件<ul><li>事务标识号</li><li>该事务执行更新前的数据块</li><li>该事务执行更新后的数据块</li></ul></li><li>每个日志记录在日志中都有一个唯一的码,叫做日志序号(简称LSN)</li></ul></li></ul></li><li>恢复策略<ul><li>事务故障的恢复:由恢复子系统利用<strong>日志文件</strong>撤消此事务已对数据库进行的修改<ul><li>反向扫描文件日志,查找该事务的更新操作,并对该事务的更新操作执行逆操作</li><li>如此处理下去,直至读到此事务的开始标记,事务故障恢复即完成</li></ul></li><li>系统故障的恢复<ul><li>正向扫描日志文件,得到Undo队列和Redo队列</li><li><strong>Undo故障发生时未完成的事务</strong>:一些未完成事务对数据库的更新已写入数据库,反向扫描日志文件,对每个UNDO事务的更新操作执行逆操作</li><li><strong>Redo已完成的事务</strong>:一些已提交事务对数据库的更新还留在缓冲区没来得及写入数据库,正向扫描日志文件,对每个REDO事务重新执行登记的操作</li><li>通俗方式<ul><li>在系统发生故障之前已经提交的事务需要重做</li><li>在系统发生故障之前开始但没有提交的需要撤销</li><li>在系统发生故障之前已经回滚的事务不做操作,相当于没有进行</li><li>系统恢复后,回滚和撤销的事务相当于没有执行,只需要考虑重做的事务</li></ul></li></ul></li><li>介质故障的恢复<ul><li>重装数据库,使数据库恢复到一致性状态<ul><li>对于静态转储的数据库副本,装入后数据库即处于一致性状态</li><li>对于动态转储的数据库副本,还须同时装入转储时刻的日志文件副本,利用与恢复系统故障相同的方法恢复</li></ul></li><li>装入转储结束时刻的日志副本,重做已完成的事务<ul><li>首先扫描日志文件,找出故障发生时已提交的事务的标识,将其记入重做队列</li><li>然后正向扫描日志文件,对重做队列中的所有事务操作进行重做。即将日志记录中“更新后的值”写入数据库</li></ul></li></ul></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 数据库系统 </tag>
</tags>
</entry>
<entry>
<title>数据库系统概论笔记(三)</title>
<link href="/posts/2023/02/22/notes/Introduction-to-Database-System-3/"/>
<url>/posts/2023/02/22/notes/Introduction-to-Database-System-3/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Introduction-to-Database-Systems.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">数据库系统概论笔记</div> <div class="tag-link-sitename"> 点击下载数据库系统概论笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="四、关系数据理论"><a href="#四、关系数据理论" class="headerlink" title="四、关系数据理论"></a>四、关系数据理论</h2><h3 id="4-1-规范化理论"><a href="#4-1-规范化理论" class="headerlink" title="4.1 规范化理论"></a>4.1 规范化理论</h3><ul><li>关系的规范化:按照一定的规范设计的关系模式,将结构复杂的关系分解成结构简单的关系,从而把不好的关系数据库模式转变成为好的关系数据库模式</li><li>简化的关系模式:$R(U,F)$,其中$U$为组成该关系的属性名集合,$F$为属性间数据的依赖关系集合</li></ul><h3 id="4-2-函数依赖(FD)"><a href="#4-2-函数依赖(FD)" class="headerlink" title="4.2 函数依赖(FD)"></a>4.2 函数依赖(FD)</h3><ul><li><p>设$R(U)$是属性集$U$上的关系模式,$X $, $Y\subseteq U$, $r$是$R(U) $上的任意一个关系,如果有对$\forall t,s\in r$,若$t[X] = s[X]$,则$t[Y] = s[Y]$,那么称<strong>X函数决定Y</strong>,或<strong>Y函数依赖于X</strong>,记作$X\rightarrow Y$,并称$X$为决定因素</p></li><li><p>函数依赖不是指关系模式R的某个或某些关系实例满足的约束条件,而是指<strong>R的所有关系实例均要满足的约束条件</strong></p></li><li><p>若$X\rightarrow Y$且$Y\rightarrow X$,则$X\leftrightarrow Y$</p></li><li><p>若$Y$不函数依赖于$X$,则$X\nrightarrow Y$</p></li><li><p>平凡函数依赖:如果$X\rightarrow Y$且$Y\subset X$,则称为平凡函数依赖</p></li><li><p>非平凡函数依赖:如果$X\rightarrow Y$且$Y\nsubseteq X$,则称为非平凡函数依赖</p></li><li><p>若不特别声明,我们讨论的都是非平凡的函数依赖</p></li><li><p><strong>部分函数依赖</strong>:如果$X\rightarrow Y$,且对于任意$X$的真子集$X’$,有$X’\nrightarrow Y$,则称$Y$对$X$完全函数依赖,记作$X{\stackrel{f}\rightarrow}Y$,否则称$Y$对$X$部分函数依赖,记作$X{\stackrel{p}\rightarrow}Y$</p><blockquote><p>只有当决定因素是组合属性时,讨论部分函数依赖才有意义</p><p>当决定因素是单属性时,只能是完全函数依赖</p><p>例如,在关系模式$S(SNO,SN,AGE,DEPT)$,决定因素为单属性$SNO$,有$SNO (SN,AGE,DEPT)$,不存在部分函数依赖</p></blockquote></li><li><p>传递函数依赖:如果$X\rightarrow Y$,$Y\rightarrow Z$,$Y\nrightarrow X$,$Y\nsubseteq X$,则称$Z$对$X$传递函数依赖</p></li><li><p>候选码:设$K$为$R(U,F)$的属性或属性组合,若$K\stackrel{f}\rightarrow U$,则称$K$为$R$的候选码</p></li><li><p>主属性:任何一个候选码中的属性称作主属性</p></li><li><p>主码:从一个关系的多个候选码中选定一个作为主码</p></li><li><p>超码:某个属性组合,其存在一个子集是候选码</p></li></ul><h3 id="4-3-范式"><a href="#4-3-范式" class="headerlink" title="4.3 范式"></a>4.3 范式</h3><ul><li>$1NF$<ul><li>关系中每一分量在语义上不可再分</li><li>即不能以集合、数组、序列、结构体等作为属性值</li></ul></li><li>$2NF$<ul><li>若$R\in 1NF$,且每个非主属性完全依赖于码,则称$R\in 2NF$</li><li>该范式<strong>消除了非主属性对码的部分依赖</strong></li><li>采用投影分解法将一个$1NF$的关系分解为多个$2NF$的关系</li><li>将一个$1NF$关系分解为多个$2NF$的关系,并不能完全消除关系模式中的各种异常情况和数据冗余</li></ul></li><li>$3NF$<ul><li>关系模式$R(U,F)$中,若不存在这样的码$X$、属性组$Y$及非主属性$Z$($Z\nsubseteq Y$),使得$X\rightarrow Y$,$Y\nrightarrow X$,$Y\rightarrow Z$成立,称$R\in 3NF$</li><li>每一个非主属性既不部分函数依赖于候选码也不传递函数依赖于候选码</li><li>该范式<strong>消除了非主属性对码的传递依赖</strong></li></ul></li><li>$BCNF$<ul><li>设$R\in 1NF$,若对于$R$的每个函数依赖$X\rightarrow Y$,若$Y$不属于$X$,则$X$必包含候选码,那么$R\in BCNF$</li><li>该范式<strong>消除了主属性对码的传递依赖或部分依赖</strong></li><li>如果$R\in 3NF$,且$R$只有一个候选码,则$R\in BCNF$</li></ul></li></ul><h3 id="4-4-函数依赖的公理系统"><a href="#4-4-函数依赖的公理系统" class="headerlink" title="4.4 函数依赖的公理系统"></a>4.4 函数依赖的公理系统</h3><ul><li><p>逻辑蕴涵:已知关系模式$R$,$U$是属性集全体,$F$是其函数依赖,$X$,$Y$是其属性子集,对于任何一个关系$r$,若函数依赖$X\rightarrow Y$都成立,则称$F$逻辑蕴涵$X\rightarrow Y$</p></li><li><p>依赖闭包:在关系模式$R$中,为$F$所逻辑蕴涵的函数依赖的全体叫做$F$的闭包,记作$F^{+}$</p></li><li><p>$X$关于$F$的闭包$X_{F^+}$</p><ul><li>$X_{F^+}={A\vert X\rightarrow A}$可由$F$根据$Armstrong$公理系统导出</li></ul></li><li><p>$Armstrong$公理系统</p><ul><li>自反律:若$Y\subseteq X\subseteq U$,则$X\rightarrow Y$为$F$所蕴含</li><li>增广律:若$X\rightarrow Y$为$F$所蕴含,且$Z\subseteq U$,则$XZ\rightarrow YZ$为$F$所蕴含</li><li>传递律:若$X\rightarrow Y$和$Y\rightarrow Z$为$F$所蕴含,则$X\rightarrow Z$为$F$所蕴含</li><li><strong>合并规则</strong>:由$X\rightarrow Y$,$X\rightarrow Z$,则$X\rightarrow YZ$</li><li><strong>伪传递规则</strong>:由$X\rightarrow Y$,$WY\rightarrow Z$,则$XW\rightarrow Z$</li><li><strong>分解规则</strong>:由$X\rightarrow Y$,$Z\subseteq Y$,则$X\rightarrow Z$</li><li>$X\rightarrow A_1A_2\cdots A_k$成立的充要条件是$X\rightarrow A_i$成立</li></ul></li><li><p>$X\rightarrow Y$能由$F$根据$Armstrong$导出的充要条件是$Y\subseteq X_{F^+}$</p><ul><li>判定$X\rightarrow Y$能否由$F$根据$Armstrong$导出转化为先求$X_{F^+}$,然后判断$Y$是否为其子集</li><li>如果$X_{F^+}=U$,则$X$是$R$的候选码</li></ul></li><li><p><strong>$X_{F^+}$的计算</strong></p><ul><li>寻找$F$中决定因素为$X$或$X$子集的函数依赖,得到这些函数依赖的右部,与$X$合并得到新的$X$</li><li>继续以上步骤,直至合并前后的属性集相同或合并后的属性集为$U$</li></ul></li><li><p><strong>候选码的计算</strong></p><ul><li>基本定义<ul><li>左部属性:只出现在$F$左边的属性</li><li>右部属性:只出现在$F$右边的属性</li><li>双部属性:出现在$F$两边的属性</li><li>外部属性:不出现在$F$的属性</li></ul></li><li>基本规则<ul><li>左部属性一定出现在任何候选码中/一定是主属性</li><li>右部属性一定不出现在任何候选码中/一定是非主属性</li><li>外部属性一定出现在任何候选码中/一定是主属性</li></ul></li><li>求解过程<ul><li>求<strong>已经确定的主属性集</strong>关于函数依赖的闭包</li><li>如果该闭包为属性全集$U$,则<strong>已经确定的主属性集</strong>为唯一候选码</li><li>如果该闭包不为属性全集$U$,则依次取左部属性和双部属性中的一个组成临时候选码,求其闭包,如果该闭包为属性全集$U$,则该临时候选码为候选码</li><li>如果第三步中仍未得到候选码,则依次取左部属性和双部属性中的<strong>2~n</strong>个组成临时候选码,求其闭包,如果该闭包为属性全集$U$,则该临时候选码为候选码</li></ul></li></ul></li><li><p>函数依赖的等价/覆盖:如果$G^+=F^+$,则$F$覆盖$G$</p></li><li><p>最小覆盖/极小函数依赖集:满足如下条件的函数依赖集</p><ul><li>$F$中任一函数依赖的右部仅含有一个属性</li><li>$F$中不存在这样的函数依赖$X\rightarrow A$,使得$F$与$F-{X\rightarrow A}$等价,即$F$中的函数依赖不能被其他函数依赖导出</li><li>$F$中不存在这样的函数依赖$X\rightarrow A$,$X$有真子集$Z$使得$F$与$F-{X\rightarrow A}\cup{Z\rightarrow A}$等价,即$F$中各函数依赖左部均为最小属性集,均不存在冗余属性</li></ul></li><li><p><strong>最小覆盖的计算</strong></p><ul><li>将$F$中所有函数依赖的右边化为单一属性<ul><li>使用合并规则</li><li>逐一检查$F$中各函数依赖$F_i:X\rightarrow Y$</li><li>若$Y=A_1A_2\cdots A_k,k\geq2$,则使用${X\rightarrow A_j\vert j=1,2,\cdots,k}$代替$X\rightarrow Y$</li></ul></li><li>去掉$F$中所有冗余的函数依赖<ul><li>逐一检查$F$中各函数依赖$F_i:X\rightarrow Y$</li><li>若$Y\in X_{(F-F_i)^+}$,则去掉该函数依赖</li></ul></li><li>去掉$F$中所有函数依赖的左边的冗余属性<ul><li>逐一检查$F$中各函数依赖$F_i:X\rightarrow Y$</li><li>若$X=B_1B_2\cdots B_m$<ul><li>考察$B_i$,$Y\in(X-B_i)_{F^+}$则以$X-B_i$代替$X$</li></ul></li></ul></li></ul></li></ul><h3 id="4-5-模式分解"><a href="#4-5-模式分解" class="headerlink" title="4.5 模式分解"></a>4.5 模式分解</h3><ul><li>模式分解:将关系模式$R<U,V>$分解为$\rho={R_1<U_1,F_1>,\cdots,R_n<U_n,F_n>}$,且$U=U_1\cup\cdots\cup U_n$,没有$U_i\subseteq U_j,1\leq i,j\leq n$,$F_i$是$F$在$U_i$上的投影</li><li>正确的模式分解<ul><li>具有无损连接性:设$\rho={R_1<U_1,F_1>,\cdots,R_n<U_n,F_n>}$是$R<U,V>$的一个分解,若对$R$上的任何一个关系$r$均有$r=r$在$\rho$中个关系模式上投影的自然连接,则称$\rho$具有无损连接性,简称$\rho$为无损分解</li><li>具有保持函数依赖性:设$\rho={R_1<U_1,F_1>,\cdots,R_n<U_n,F_n>}$是$R<U,V>$的一个分解,若$F$所逻辑蕴涵的函数依赖一定为分解后所有的关系模式中的函数依赖$F_i$所逻辑蕴涵,即$F^+=(F_1\cup F_2\cup\cdots\cup F_n)^+$,则称$\rho$具有保持函数依赖性</li><li>如果一个分解具有无损连接性,则它能够保证不丢失信息</li><li>如果一个分解保持了函数依赖,则它可以减轻或解决各种异常情况</li></ul></li></ul><h2 id="五、查询优化"><a href="#五、查询优化" class="headerlink" title="五、查询优化"></a>五、查询优化</h2><h3 id="6-1-查询处理"><a href="#6-1-查询处理" class="headerlink" title="6.1 查询处理"></a>6.1 查询处理</h3><img src="https://picbed.cloudchewie.com/blog/post/Introduction-to-Database-System-3/Chp1-%E6%9F%A5%E8%AF%A2%E8%AE%A1%E5%88%92%E4%B8%BE%E4%BE%8B.png!blogimg" class="" width="400" title="查询计划举例"><ul><li><p>查询处理流程</p><ul><li>操作算子以树的形式进行组织</li><li>数据流从叶子结点流向根节点</li><li>根节点的输出是查询的结构</li></ul></li><li><p>迭代模型</p><img src="https://picbed.cloudchewie.com/blog/post/Introduction-to-Database-System-3/Chp1-%E8%BF%AD%E4%BB%A3%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="迭代模型"><ul><li>每个算子执行$Next$函数,调用得到一个下层提交的元组或一个空值标记NULL Maker</li></ul></li><li><p>物化模型</p><img src="https://picbed.cloudchewie.com/blog/post/Introduction-to-Database-System-3/Chp1-%E7%89%A9%E5%8C%96%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="物化模型"><ul><li>每个算子一次性获取所有输出,传递元组列表给父节点</li></ul></li><li><p>向量模型</p><img src="https://picbed.cloudchewie.com/blog/post/Introduction-to-Database-System-3/Chp1-%E5%90%91%E9%87%8F%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="向量模型"><ul><li>基本框架同迭代模型,但是每次提交一批元组(Batch)</li></ul></li></ul><h3 id="6-2-代数优化"><a href="#6-2-代数优化" class="headerlink" title="6.2 代数优化"></a>6.2 代数优化</h3><h4 id="6-2-1-代数优化的基本准则"><a href="#6-2-1-代数优化的基本准则" class="headerlink" title="6.2.1 代数优化的基本准则"></a>6.2.1 代数优化的基本准则</h4><ul><li>选择运算尽可能先做</li><li>在执行连接操作前对关系适当进行预处理</li><li>投影运算和选择运算同时做</li><li>某些选择运算与在其前面执行的笛卡尔积合并为连接运算</li></ul><h4 id="6-2-2-关系代数等价变换规则"><a href="#6-2-2-关系代数等价变换规则" class="headerlink" title="6.2.2 关系代数等价变换规则"></a>6.2.2 关系代数等价变换规则</h4><ul><li>连接、笛卡尔积交换律:$E_1\times E_2=E_2\times E_1$、$E_1\Join E_2=E_2\Join E_1$</li><li>连接、笛卡尔积结合律:$(E_1\times E_2)\times E_3=E_1\times(E_2\times E_3)$、$(E_1\Join E_2)\Join E_3=E_1\Join(E_2\Join E_3)$</li><li>投影的串接定律:$\prod_{A_1,A_2,\cdots,A_n}(\prod_{B_1,B_2,\cdots,B_m}(E))=\prod_{A_1,A_2,\cdots,A_n}(E)$,其中${A_1,A_2,\cdots,A_n}$是${B_1,B_2,\cdots,B_m}$的子集</li><li>连接的串接定律:$\sigma_{F_1}(\sigma_{F_2}(E))=\sigma_{F_1\land F_2}(E)$</li><li>选择与投影的交换律<ul><li>如果选择条件$F$只涉及$A_1,\cdots,A_n$,则$\sigma_F(\prod_{A_1,A_2,\cdots,A_n}(E))=\prod_{A_1,A_2,\cdots,A_n}(\sigma_F(E))$</li><li>如果选择条件$F$含有不属于$A_1,\cdots,A_n$的属性$B_1,\cdots,B_n$,则$\sigma_F(\prod_{A_1,A_2,\cdots,A_n}(E))=\prod_{A_1,A_2,\cdots,A_n}(\sigma_F(\prod_{A_1,A_2,\cdots,A_n,B_1,B_2,\cdots,B_m}(E)))$</li></ul></li><li>选择、笛卡尔积交换律<ul><li>如果选择条件$F$只涉及$E_1$,则$\sigma_F(E_1\times E_2)=\sigma_F(E_1)\times E_2$</li><li>设$F=F_1\land F_2$,如果选择条件$F_1$只涉及$E_1$且选择条件$F_2$只涉及$E_2$,则$\sigma_F(E_1\times E_2)=\sigma_{F_1}(E_1)\times \sigma_{F_2}(E_2)$</li><li>设$F=F_1\land F_2$,如果选择条件$F_1$只涉及$E_1$且选择条件$F_2$涉及$E_1$和$E_2$两者的属性,则$\sigma_F(E_1\times E_2)=\sigma_{F_1}(E_1)\times \sigma_{F_2}(E_2)$</li></ul></li><li>选择对自然连接的分配律:$\sigma(E_1\times E_2)=\sigma(E_1)\times\sigma(E_2)$</li><li>投影与笛卡尔积的交换:$\prod_{A_1,A_2,\cdots,A_n,B_1,B_2,\cdots,B_m}(E_1\times E_2)=\prod_{A_1,A_2,\cdots,A_n}(E_1)\times \prod_{B_1,B_2,\cdots,B_m}(E_2)$</li></ul><h4 id="6-2-3-代数优化的步骤"><a href="#6-2-3-代数优化的步骤" class="headerlink" title="6.2.3 代数优化的步骤"></a>6.2.3 代数优化的步骤</h4><ul><li>根据SQL语句画出语法树</li><li>将语法树转换为关系代数语法树</li><li>通过代数优化得到优化后的关系代数语法树</li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 数据库系统 </tag>
</tags>
</entry>
<entry>
<title>计算机网络与通信笔记(一)</title>
<link href="/posts/2022/11/12/notes/Computer-Network-and-Communication-1/"/>
<url>/posts/2022/11/12/notes/Computer-Network-and-Communication-1/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Computer-Network-and-Communication.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">计算机网络与通信笔记</div> <div class="tag-link-sitename"> 点击下载计算机网络与通信笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="考试重点"><a href="#考试重点" class="headerlink" title="考试重点"></a>考试重点</h2><ul><li>协议栈:五层协议的名称、功能、协议、分组名称、各种设备处理的协议</li><li>基础知识:时延的类型与计算</li><li>应用层:HTTP协议报文、HTTP协议非持续连接与持续连接、SMTP协议与衍生协议的功能</li><li>运输层:可靠数据传输(StopWait、GBN、SN、TCP)、TCP协议的拥塞控制、CRC校验</li><li>网络层数据层面:子网划分、CIDR、NAT、IP协议报文</li><li>网络层控制层面:路由转发、链路状态选择算法、距离向量选择算法</li><li>链路层:MAC地址、ARP协议、以太网协议、争用期、交换机</li></ul><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><ul><li>运输层:TCP建立连接的过程、TCP的流量控制、TCP Tahoe TCP Reno</li><li>网络层:DHCP、ICMP、路由器的三种交换结构、LS、OSPF、DV、RIP、BGP协议</li><li>网关路由器</li><li>链路层:码分多址</li></ul><h2 id="一、计算机网络与因特网"><a href="#一、计算机网络与因特网" class="headerlink" title="一、计算机网络与因特网"></a>一、计算机网络与因特网</h2><h3 id="1-1-什么是因特网"><a href="#1-1-什么是因特网" class="headerlink" title="1.1 什么是因特网"></a>1.1 什么是因特网</h3><h4 id="1-1-1-具体构成"><a href="#1-1-1-具体构成" class="headerlink" title="1.1.1 具体构成"></a>1.1.1 具体构成</h4><ul><li><p>硬件层面</p><ul><li><p>主机/端系统:诸如桌面$PC$、$Linux$工作站、平板电脑等与因特网互联的设备</p><blockquote><p>端系统也称主机$(host)$,因为其能运行应用程序;主机又可以分为客户$(client)$和服务器$(server)$</p></blockquote></li><li><p>端系统通过<strong>通信链路</strong>和<strong>分组交换机</strong>连接到一起</p></li><li><p>通信链路:由不同类型的物理媒体组成,包括同轴电缆、铜线、光纤和无线电频谱</p></li><li><p>分组交换机:用于连接不同的通信链路传输数据,包括路由器和链路层交换机等</p></li><li><p>端系统通过<strong>因特网服务提供商</strong>$(ISP)$接入因特网</p></li></ul></li><li><p>软件层面</p><ul><li><strong>协议</strong>$(protocol)$控制着因特网中信息的接受和发送,其主要协议统称为$TCP/IP$</li><li>协议定义了在两个或多个通信实体之间交换的报文的格式和顺序,以及报文发送和/或接收报文所采取的动作</li></ul></li></ul><h3 id="1-2-网络边缘"><a href="#1-2-网络边缘" class="headerlink" title="1.2 网络边缘"></a>1.2 网络边缘</h3><h4 id="1-2-1-接入网"><a href="#1-2-1-接入网" class="headerlink" title="1.2.1 接入网"></a>1.2.1 接入网</h4><ul><li><p>基本概念</p><ul><li><p>接入网:将端系统物理连接到其<strong>边缘路由器</strong>的网络</p><blockquote><p>接入网的本质作用:通过各种方式使主机连接到路由器,以使得任意两个端系统间能够相互通信</p></blockquote></li><li><p>边缘路由器:端系统连接到其他任何远程端系统的路径上的<strong>第一台</strong>路由器</p></li></ul></li><li><p>接入方式</p><ul><li><p>点对点方式</p><ul><li><p>数据用户线$DSL$:通过本地电话公司获得互联网接入,本地电话公司即为其$ISP$</p><blockquote><p>不对称数字用户线$ADSL$通过在不同的频段进行编码使得电话线能够同时承载数据信号与电话信号。</p></blockquote></li><li><p>光纤到户$FTTH$:将光纤从本地中心局直接连接到房间,通过光猫(光调制解调器)转换光电信号,使用双绞线连接电脑</p></li><li><p>卫星:电脑连接卫星信号接收机,通过无线电波与卫星相连,卫星又通过无线电波与地面上另一接收机相连</p></li></ul></li><li><p>以太网方式</p><ul><li>有线以太网:端系统通过双绞线连到以太网,以太网连接至边缘路由器</li><li>$WiFi$:端系统连到$WiFi$,$WiFi$连接至边缘路由器</li></ul></li><li><p>广域无线接入方式</p><ul><li>由电信运营商提供,如$3G$、$4G$、$5G$</li></ul></li></ul></li></ul><h4 id="1-2-2-物理媒体"><a href="#1-2-2-物理媒体" class="headerlink" title="1.2.2 物理媒体"></a>1.2.2 物理媒体</h4><ul><li>导引型媒体<ul><li>双绞铜线:由两根并行铜线组成,如传统电话线</li><li>同轴电缆:由两根同心铜线组成,如电缆</li><li>光纤:能够引导光脉冲,误码率低</li></ul></li><li>非导引型媒体<ul><li>无线电波:通过电磁频谱传递信号</li></ul></li></ul><h3 id="1-3-网络核心"><a href="#1-3-网络核心" class="headerlink" title="1.3 网络核心"></a>1.3 网络核心</h3><h4 id="1-3-1-电路交换"><a href="#1-3-1-电路交换" class="headerlink" title="1.3.1 电路交换"></a>1.3.1 电路交换</h4><ul><li><p>特点(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/circuit_switching.php">在线交互程序</a>)</p><ul><li>数据交换前需建立一条从发送端到接收端的物理通路</li><li>在数据交换的全部时间,用户始终占用端到端的固定传输信道,且<strong>只能为其所用</strong></li><li>所有用户平分网络链路的传输容量,且<strong>传输速率恒定</strong></li><li>交换双方可以<strong>实时</strong>进行数据交换,不会存在延迟</li><li>适合传送<strong>大量数据</strong>,传送分组时间远大于连接建立时间</li></ul></li><li><p>复用方式</p><ul><li><p>频分复用$FDM$</p><ul><li>所有连接共用链路的频谱,每个连接专用一个频段,其宽度称为<strong>带宽</strong></li><li>每条连接连续地得到部分带宽</li></ul></li><li><p>时分复用$TDM$</p><ul><li>时间被划分为固定期间的帧,每个帧被划分为固定数量的时隙</li><li>链路为每个连接在每个帧中指令一个时隙,这些时隙由每个连接单独使用</li><li>每条连接周期性地得到全部带宽</li></ul></li><li><p>复用方式的对比</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-1/Chp1-%E9%A2%91%E5%88%86%E5%A4%8D%E7%94%A8%E4%B8%8E%E6%97%B6%E5%88%86%E5%A4%8D%E7%94%A8.png!blogimg" class="" width="400" title="频分复用与时分复用"></li><li><p>举例说明:</p><ul><li>问:设所有链路速率皆为$1.536Mbps$,每条链路使用有$24$个时隙的$TDM$,建立端到端的电路需要$500$毫秒,则计算通过电路交换网络将一个$640000$比特长的文件从主机$A$传送到主机$B$需要多长时间</li><li>答:$(640kb)/(1.536Mbps/24)+0.5=10.5s$</li></ul></li><li><p>缺点:</p><ul><li>计算机之间的数据交换往往具有<strong>突发性</strong>和<strong>间歇性</strong>特征,而对电路交换而言,用户支付的费用则是按用户占用线路的时间来收费</li><li>只要在通话双方建立的通路中任何一点出了故障,就必须重新拨号建立新连接,这对紧急和重要通信很不利。</li></ul></li></ul></li></ul><h4 id="1-3-2-分组交换"><a href="#1-3-2-分组交换" class="headerlink" title="1.3.2 分组交换"></a>1.3.2 分组交换</h4><ul><li><p>特点</p><ul><li><p>将要发送的报文分解成若干个小部分,称为分组</p><blockquote><p>每个分组都通过通信链路和分组交换机传送,以等于该链路最大传输速率地速度通过通信链路</p><p>通过某链路发送一个$L$比特的分组,链路传输速率为$R\enspace bit/s$,则传输分组的时间为$L/R$秒</p></blockquote></li><li><p>存储转发传输:交换机能够开始向输出链路传输该分组的第一个比特前,<strong>必须接收到整个分组</strong></p></li><li><p>每个分组传输的链路可能不同,且存在冗余路由</p><blockquote><p>在分组传输前<strong>不必预先确定分组的传输路径</strong>,而是在传输到某个分组交换机后根据转发表查找转发端口</p></blockquote></li><li><p>网络核心中每个交换结点均为共享结点</p></li><li><p>适合传送<strong>突发数据</strong></p></li></ul></li><li><p>电路类型</p><ul><li>数据报网络</li><li>虚电路网络(结合电路连接的优点)<ul><li>虚电路需要<strong>建立连接</strong>,即建立虚电路链路</li><li><strong>在建立连接时决定链路的路由</strong>,在整个连接过程中保持不变</li><li>在链路通过的每个节点,预留一定的资源</li><li>每个分组携带一个标识(虚电路号),根据该标识知道该从哪个虚电路传输数据</li><li>虚电路如果不再使用,需要<strong>释放相关的资源</strong></li></ul></li></ul></li><li><p>与电路交换比较(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/ps_versus_cs.php">在线交互程序</a>)</p><ul><li><p>在相同条件下,分组交换能够比电路交换支持更多的用户</p><blockquote><p>设一条$1Mbps$的链路,每个用户需要$100kbps$,电路交换模式下仅支持$10$个用户;设分组交换下$1$个用户活跃的概率为$0.1$,在$35$个用户条件下,$11$个及以上用户同时活动的概率为$0.0004$,即$10$个及$10$个以内用户同时活跃的概率为$0.9996$,基本上与电路交换性能相当</p></blockquote></li><li><p>当用户数较少时,分组交换能够获得比电路交换更好的性能</p><blockquote><p>同一时刻仅有一个用户传输$1M$的数据,电路交换需要$10s$,分组交换需要$1s$</p></blockquote></li><li><p>在数据量大时,分组交换的传输时延比电路交换大</p><blockquote><p>通过由$n$条速率均为$R$的链路组成的路径,从源到目的地发送一个分组的时延为$d_{end-to-end}=N\frac{L}{R}$</p></blockquote></li></ul></li><li><p>缺点</p><ul><li>分组在各结点存储转发时因要排队<strong>会造成一定的时延</strong>,当网络通信量过大时,这种时延可能会很大。</li><li>各分组必须携带一定的<strong>控制信息(说明信息)</strong>,从而带来额外开销。</li></ul></li></ul><h4 id="1-3-3-ISP-网络模型"><a href="#1-3-3-ISP-网络模型" class="headerlink" title="1.3.3 $ISP$网络模型"></a>1.3.3 $ISP$网络模型</h4><ul><li><p>各种$ISP$互相连在一起</p></li><li><p>低级$ISP$可以连入高级$ISP$进行互通</p></li><li><p>同级$ISP$之间通过$IXP$和对等链路进行<strong>对等</strong>链接</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-1/Chp1-ISP%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="ISP模型"></li></ul><h3 id="1-4-分组交换网络的衡量"><a href="#1-4-分组交换网络的衡量" class="headerlink" title="1.4 分组交换网络的衡量"></a>1.4 分组交换网络的衡量</h3><h4 id="1-4-1-时延"><a href="#1-4-1-时延" class="headerlink" title="1.4.1 时延"></a>1.4.1 时延</h4><p>节点总时延主要包括节点处理时延、排队时延、传输时延、传播时延</p><ul><li>节点处理时延$(d_{proc})$<ul><li>检查分组首部并决定该分组导向何处所需的时间</li><li>检查比特级别的差错所需的时间</li></ul></li><li><strong>排队时延</strong>$(d_{queue})$<ul><li>在队列中,分组在链路上等待传输的时间,时延长度取决于<strong>先到达的正在排队的分组数量</strong></li></ul></li><li>传输时延$(d_{trans})$<ul><li>将分组的所有比特推向输出链路所需的时间(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/one-hop-delay.php">在线交互程序</a>)</li><li>等于分组的长度除以<strong>链路传输速率</strong>,即$L/R$,其中$R$的单位为$bps、kbps、Mbps$等</li></ul></li><li>传播时延$(d_{prop})$<ul><li>从输出链路的起点到目的地传播所需的时间,数据以链路的传播速率传播,取决于链路的物理媒体</li><li>等于两台路由器间的距离除以<strong>链路传播速率</strong>,即$d/s$</li></ul></li><li>关于传输时延和传播时延(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/caravan.php">在线交互程序</a>,<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/end-end-delay.php">在线交互程序</a>)</li></ul><p>总传输时延$d_{nodal}=d_{proc}+d_{queue}+d_{trans}+d_{prop}$</p><h4 id="1-4-2-丢包"><a href="#1-4-2-丢包" class="headerlink" title="1.4.2 丢包"></a>1.4.2 丢包</h4><ul><li><p>排队时延分析(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/qdelay.php">在线交互程序</a>)</p><ul><li><p>设链路传输速率为$R\enspace bps$,分组大小为$L\enspace bit/pkt$,分组到达队列的平均速率$a\enspace pkt/s$</p></li><li><p>则比特到达队列的平均速率为$La\enspace bps$,流量强度为$La/R$</p></li><li><p>$La/R\sim0$:平均排队时延很小,甚至为0</p></li><li><p>$La/R < 1$:时延较小,且会随时间推延而变小</p></li><li><p>$La/R = 1$:时延不会变化,具体数值取决于当时队列长度</p></li><li><p>$La/R > 1$: 比特到达队列的平均速率远超过从队列传输出去的速率,队列将无限增加,排队时延趋于$\infty$,用户将难以接受</p></li></ul></li><li><p>丢包</p><ul><li>当队列已满时,分组将会被路由器丢弃,分组即会丢失</li><li>丢失的分组已经传输到网络核心,但是绝不会从网络发送到目的地</li></ul></li><li><p>在$Windows$操作系统使用$tracert$命令、在$Linux$和$Mac$操作系统使用$traceroute$命令以测试端到端时延</p></li></ul><h4 id="1-4-3-吞吐量"><a href="#1-4-3-吞吐量" class="headerlink" title="1.4.3 吞吐量"></a>1.4.3 吞吐量</h4><ul><li>P2P系统<ul><li>服务器$\Longrightarrow$路由器$\Longrightarrow$客户端<ul><li>使用$R_s$表示服务器与路由器之间的链路速率,$R_c$表示路由器与客户端之间的链路速率</li><li>服务器不能以快于$R_s$的速率向链路发送比特,路由器也不能以大于$R_c$的速率发送比特</li><li>当$R_s>R_c$时,路由器端将会出现比特等待队列,当$R_s<R_c$时,数据将流畅地传输到客户端</li><li>其吞吐量被定义为$min{R_c,R_s}$</li></ul></li><li>服务器$\Longrightarrow$路由器1$\Longrightarrow$路由器2$\Longrightarrow\cdots\Longrightarrow$路由器$N\Longrightarrow$客户端<ul><li>吞吐量为$min{R_1,R_2,\cdots,R_N}$</li></ul></li></ul></li><li>当网络核心的链路速率远大于接入网时,吞吐量的主要限制因素便为<strong>接入网</strong></li><li>瓶颈链路:在端到端路径上限制了端到端平均吞吐量的链路(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/end-end-throughput-simple.php">在线交互程序</a>)</li></ul><h3 id="1-5-协议层次及其服务模型"><a href="#1-5-协议层次及其服务模型" class="headerlink" title="1.5 协议层次及其服务模型"></a>1.5 协议层次及其服务模型</h3><h4 id="1-5-1-协议分层"><a href="#1-5-1-协议分层" class="headerlink" title="1.5.1 协议分层"></a>1.5.1 协议分层</h4><ul><li><p>基本概念</p><ul><li><p><strong>实体</strong>是任何可以发送和接收信息的硬件和软件进程。通常是一个特定的软件模块</p></li><li><p>不同机器上包含对应层的实体称为<strong>对等体</strong>,如客户端的运输层对应服务端的运输层</p></li><li><p><strong>服务</strong>指为保证上层对等体之间能互相通信,下层向上层提供的功能</p></li><li><p><strong>接口</strong>位于每对相邻层之间,定义了下层向上层提供的原语操作和服务</p></li><li><p><strong>协议数据单元</strong>$(PDU)$是对等层次上传送数据的单位</p></li><li><p><strong>服务数据单元</strong>$(SDU)$是层与层之间交换数据的单位</p></li><li><p><strong>网络体系结构</strong>是层和协议的集合</p></li><li><p><strong>协议栈</strong>指一个特定的系统所使用的一系列协议(每层一组协议)</p></li></ul></li><li><p>因特网的五层结构(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/layers.php">在线交互程序</a>)</p></li></ul><table><thead><tr><th align="center">层次</th><th align="center">功能</th><th align="center">协议</th><th align="center">分组名称</th></tr></thead><tbody><tr><td align="center">应用层</td><td align="center">直接为用户的网络应用程序提供服务</td><td align="center">HTTP、SMTP、FTP、DNS</td><td align="center">报文</td></tr><tr><td align="center">表示层</td><td align="center"><strong>在OSI模型中</strong>,统一表示数据的含义</td><td align="center">-</td><td align="center">-</td></tr><tr><td align="center">会话层</td><td align="center"><strong>在OSI模型中</strong>,数据交换的定界和同步</td><td align="center">-</td><td align="center">-</td></tr><tr><td align="center">运输层</td><td align="center">在不同主机的进程间数据传送</td><td align="center">TCP、UDP</td><td align="center">报文段</td></tr><tr><td align="center">网络层</td><td align="center">在不同主机间数据传送;选择合适的路由传输运输层分组</td><td align="center">网际协议IP,路由协议</td><td align="center">数据报</td></tr><tr><td align="center">链路层</td><td align="center">网络相邻结点间数据传送</td><td align="center">PPP、以太网</td><td align="center">帧</td></tr><tr><td align="center">物理层</td><td align="center">在线路上传输比特流</td><td align="center">-</td><td align="center">-</td></tr></tbody></table><h4 id="1-5-2-网络分层模型"><a href="#1-5-2-网络分层模型" class="headerlink" title="1.5.2 网络分层模型"></a>1.5.2 网络分层模型</h4><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-1/Chp1-%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84%E7%9A%84%E6%95%B0%E6%8D%AE%E6%B5%81%E5%8A%A8.png!blogimg" class="" width="400" title="层次结构的数据流动">]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机网络 </tag>
</tags>
</entry>
<entry>
<title>计算机网络与通信笔记(二)</title>
<link href="/posts/2022/11/12/notes/Computer-Network-and-Communication-2/"/>
<url>/posts/2022/11/12/notes/Computer-Network-and-Communication-2/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Computer-Network-and-Communication.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">计算机网络与通信笔记</div> <div class="tag-link-sitename"> 点击下载计算机网络与通信笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="二、应用层"><a href="#二、应用层" class="headerlink" title="二、应用层"></a>二、应用层</h2><h3 id="2-1-应用层协议原理"><a href="#2-1-应用层协议原理" class="headerlink" title="2.1 应用层协议原理"></a>2.1 应用层协议原理</h3><h4 id="2-1-1-进程通信"><a href="#2-1-1-进程通信" class="headerlink" title="2.1.1 进程通信"></a>2.1.1 进程通信</h4><ul><li><p>客户与服务器进程</p><ul><li><p>网络应用程序运行后,就变成了网络应用进程</p></li><li><p>两个在不同端系统的进程,通过跨越计算机网络交换报文而相互通信</p></li><li><p>网络应用程序由成对进程组成,在一对进程的通信会话场景中,发起通信的进程称为<strong>客户</strong>,等待联系的进程称为<strong>服务器</strong></p></li></ul></li><li><p>进程与计算机网络的接口</p><ul><li>进程通过一个称为<strong>套接字</strong>$(Socket)$的软件接口向网络发送报文和从网络接收报文</li><li>套接字是同一台主机内应用层与运输层的接口,也成为应用程序和网络之间的<strong>应用程序编程窗口</strong></li></ul></li><li><p>进程寻址</p><ul><li>为了与目的主机上进程的通信,需要定义<strong>目的主机的地址</strong>和<strong>目的主机中指定接收进程的标识符</strong></li><li>目的主机地址由$32$位的$IP$地址标识</li><li>目的进程地址由$16$位目的地端口号$Port\enspace Number$标识,如$Web$服务器的端口号为$80$,$SMTP$的端口号为$25$</li><li>套接字长度为$48$位</li></ul></li></ul><h4 id="2-1-2-运输服务简介"><a href="#2-1-2-运输服务简介" class="headerlink" title="2.1.2 运输服务简介"></a>2.1.2 运输服务简介</h4><ul><li><p>$TCP$服务</p><ul><li><strong>面向连接</strong>:在报文流动前,$TCP$让客户和服务器互相交换运输层控制信息以为分组运输做好准备(握手),此时,一个$TCP$连接在两个进程的套接字直接建立</li><li><strong>可靠性</strong>:无差错、无字节丢失与冗余、顺序地传输分组</li><li><strong>拥塞控制机制</strong>:当网络拥塞时,抑制发送进程</li><li>不提供加密机制</li></ul></li><li><p>$UDP$服务</p><ul><li>没有握手过程</li><li>不可靠数据传送服务</li><li>没有拥塞控制机制</li><li>不提供加密机制</li></ul></li><li><p>$SSL$安全套接字层</p><ul><li>提供加密的$TCP$连接</li><li>提供数据完整性和端点鉴别</li></ul></li><li><p>应用层协议与支撑的运输层协议</p></li></ul><table><thead><tr><th align="center">应用</th><th align="center">应用层协议</th><th align="center">支撑的运输层协议</th></tr></thead><tbody><tr><td align="center">电子邮件</td><td align="center">$SMTP$</td><td align="center">$TCP$</td></tr><tr><td align="center">远程终端访问</td><td align="center">$Telnet$</td><td align="center">$TCP$</td></tr><tr><td align="center">$Web$、流式多媒体</td><td align="center">$HTTP$</td><td align="center">$TCP$</td></tr><tr><td align="center">文件传输</td><td align="center">$FTP$</td><td align="center">$TCP$</td></tr><tr><td align="center">因特网电话</td><td align="center">$SIP$、$RTP$</td><td align="center">$UDP$或$TCP$</td></tr></tbody></table><h3 id="2-2-Web-和-HTTP"><a href="#2-2-Web-和-HTTP" class="headerlink" title="2.2 $Web$和$HTTP$"></a>2.2 $Web$和$HTTP$</h3><h4 id="2-2-1-HTTP-概述"><a href="#2-2-1-HTTP-概述" class="headerlink" title="2.2.1 $HTTP$概述"></a>2.2.1 $HTTP$概述</h4><ul><li><p>相关概念</p><ul><li><p>$HTTP$全称为超文本传输协议$(HyperText\enspace Transfer\enspace Protocol)$</p></li><li><p>$Web$页面由<strong>对象</strong>组成,如$HTML$文件,$JPEG$图像</p></li><li><p>通过$URL$地址引用对象,$URL$地址由<strong>存放对象的服务器地址</strong>和<strong>对象的路径</strong>组成</p><blockquote><p> 如对于$URL$地址<a href="http://cloudchewie.com/index.html%EF%BC%9A">http://cloudchewie.com/index.html:</a></p><p>$cloudchewie.com$为主机名,$/index.html$为对象路径</p></blockquote></li><li><p>$Web\enspace Browser$实现了$HTTP$的客户端,$Web\enspace Server$实现了$HTTP$的服务端,用于存储$Web$对象,每个对象由$URL$路径访问,因此$Web$是典型的$C/S$模式</p></li><li><p>$HTTP$负责定义客户向服务器请求$Web$页面的方式以及服务器向客户返回$Web$页面的方式,其传递的报文称为$HTTP$报文</p></li><li><p>客户向其套接字接口发送$HTTP$请求报文并从其套接字接口接收$HTTP$响应报文;当客户发送报文后,该报文即脱离客户控制而进入$TCP$控制,从而使得应用层协议$HTTP$无需关心报文丢失和运输层的实现细节</p></li><li><p>$HTTP$服务器不保存关于客户的任何信息,是一个<strong>无状态协议</strong></p></li></ul></li><li><p>非持续连接</p><ul><li><p>每个$TCP$连接只传输一个请求报文和一个响应报文</p></li><li><p>当客户接收$HTTP$响应报文后,$TCP$连接关闭</p></li><li><p>如果需要继续发送请求,需要建立全新的$TCP$连接</p></li><li><p>可以使用<strong>并行的连接</strong>改善缩短响应时间</p><blockquote><p>$TCP$服务是建立在连接之上的,每次建立连接前,都需要进行三次握手过程,如下图所示:</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-2/Chp2-HTTP%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="HTTP模型"><ol><li>客户端向服务器发送一个小的$TCP$报文段</li><li>服务器用一个小的$TCP$报文段进行确认和响应</li><li>客户向服务器返回确认并发送$HTTP$请求报文</li><li>在这之后,服务器开始发送文件到客户</li></ol><p>其中,环节$1-2$占用了一个往返时间$RTT$,环节$3-4$占用了一个往返时间$RTT$并且耗费了传输文件的时间</p><p><strong>因此,在非持续连接的HTTP下,每传送一个对象,就需要经受两个RTT的交付时延</strong></p></blockquote></li></ul></li><li><p>持续连接</p><ul><li>服务器发送$HTTP$响应报文后保持$TCP$连接,使得客户的后续请求继续使用该连接进行传送</li><li>当该连接经过一定时间间隔(可配置的超时间隔)仍未使用,其服务器就将关闭该连接</li></ul></li></ul><h4 id="2-2-2-HTTP-报文"><a href="#2-2-2-HTTP-报文" class="headerlink" title="2.2.2 $HTTP$报文"></a>2.2.2 $HTTP$报文</h4><ul><li><p>$HTTP$请求报文(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/http-get.php">在线交互程序</a>)</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">#典型的HTTP请求报文</span><br><span class="line">GET /index.html HTTP/1.1\r\n</span><br><span class="line"><span class="attribute">Host</span><span class="punctuation">: </span>www-net.cs.umass.edu\r\n</span><br><span class="line"><span class="attribute">User-Agent</span><span class="punctuation">: </span>Mozilla/5.0\r\n</span><br><span class="line"><span class="attribute">Accept</span><span class="punctuation">: </span>text/html,application/xhtml+xml\r\n</span><br><span class="line"><span class="attribute">Accept-Language</span><span class="punctuation">: </span>en-us,en;q=0.5\r\n</span><br><span class="line"><span class="attribute">Accept-Encoding</span><span class="punctuation">: </span>gzip,deflate\r\n</span><br><span class="line"><span class="attribute">Accept-Charset</span><span class="punctuation">: </span>ISO-8859-1,utf-8;q=0.7\r\n</span><br><span class="line"><span class="attribute">Keep-Alive</span><span class="punctuation">: </span>115\r\n</span><br><span class="line"><span class="attribute">Connection</span><span class="punctuation">: </span>keep-alive\r\n</span><br><span class="line">\r\n</span><br></pre></td></tr></table></figure><ul><li>请求报文的第一行称为<strong>请求行</strong>,包括方法字段、$URL$字段、$HTTP$版本字段</li></ul></li></ul><table><thead><tr><th align="center">方法字段</th><th align="center">主要作用</th></tr></thead><tbody><tr><td align="center">$GET$</td><td align="center">向服务器请求指定$URL$的对象</td></tr><tr><td align="center">$POST$</td><td align="center">用于向服务器提交表单数据也可以同时请求一个$Web$页面</td></tr><tr><td align="center">$DELETE$</td><td align="center">返回响应报文,不包含请求的对象</td></tr><tr><td align="center">$PUT$</td><td align="center">上传的文件放在实体主体字段中,目标路径由$URL$字段标明</td></tr><tr><td align="center">$HEAD$</td><td align="center">删除$URL$字段中指定的文件</td></tr></tbody></table><ul><li><p>其余行称为<strong>首部行</strong></p><ul><li>$Host$指明了对象所在的主机</li><li>$Connection$指明非持续连接$(Close)$和持续连接$(keep-alive)$</li><li>$Keep-Alive$指明持续连接的超时间隔</li><li>$User-agent$指明用户浏览器类型,有助于服务器根据不同的用户代理发送相同对象的不同版本</li><li>$Accept-*$指用户想要得到特定语言、编码格式、字符集的该对象</li></ul></li><li><p>结尾单独一行回车、换行表示报文首部结束</p></li><li><p><strong>实体体</strong>,在首部行之后的请求体</p><ul><li>当发送$GET$请求时,实体体为空;</li><li>当用户填写表单并发送$POST$请求时,实体体即为用户填写的表单;</li><li>当用户填写表单时,也可以使用$GET$方法,如<code>/learning/search?key=banana&lang=zh</code></li></ul></li><li><p>$HTTP$请求报文的通用格式</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-2/Chp2-HTTP%E8%AF%B7%E6%B1%82%E6%8A%A5%E6%96%87%E9%80%9A%E7%94%A8%E6%A0%BC%E5%BC%8F.png!blogimg" class="" width="400" title="HTTP请求报文通用格式"></li><li><p>$HTTP$响应报文(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/http-response.php">在线交互程序</a>)</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">#典型的HTTP响应报文</span><br><span class="line"><span class="meta">HTTP/1.1</span> <span class="number">200</span> OK\r\n</span><br><span class="line"><span class="attribute">Date</span><span class="punctuation">: </span>Sun, 26 Sep 2010 20:09:20 GMT\r\n</span><br><span class="line"><span class="attribute">Server</span><span class="punctuation">: </span>Apache/2.0.52 (CentOS)\r\n</span><br><span class="line"><span class="attribute">Last-Modified</span><span class="punctuation">: </span>Tue, 30 Oct 2007 17:00:02 GMT\r\n</span><br><span class="line"><span class="attribute">ETag</span><span class="punctuation">: </span>"17dc6-a5c-bf716880"\r\n</span><br><span class="line"><span class="attribute">Accept-Ranges</span><span class="punctuation">: </span>bytes\r\n</span><br><span class="line"><span class="attribute">Content-Length</span><span class="punctuation">: </span>2652\r\n</span><br><span class="line"><span class="attribute">Keep-Alive</span><span class="punctuation">: </span>timeout=10, max=100\r\n</span><br><span class="line"><span class="attribute">Connection</span><span class="punctuation">: </span>Keep-Alive\r\n</span><br><span class="line"><span class="attribute">Content-Type</span><span class="punctuation">: </span>text/html; charset=ISO-8859-1\r\n</span><br><span class="line">\r\n</span><br><span class="line">data data data data data ...</span><br></pre></td></tr></table></figure><ul><li>响应报文的第一行称为<strong>初始状态行</strong>,包括协议版本字段、状态码、状态信息</li></ul></li></ul><table><thead><tr><th align="center">状态码</th><th align="center">状态信息</th><th align="center">说明</th></tr></thead><tbody><tr><td align="center">$200$</td><td align="center">$OK$</td><td align="center">请求成功</td></tr><tr><td align="center">$301$</td><td align="center">$Moved\enspace Permanently$</td><td align="center">请求的对象被永久转移,新的$URL$定义在$Location$首部行</td></tr><tr><td align="center">$400$</td><td align="center">$Bad\enspace Request$</td><td align="center">通用差错代码,请求不能被服务器理解</td></tr><tr><td align="center">$404$</td><td align="center">$Not\enspace Found$</td><td align="center">被请求的对象不在服务器</td></tr><tr><td align="center">$500$</td><td align="center">$HTTP\enspace Version\enspace Not\enspace Supported$</td><td align="center">服务器不支持请求报文中的$HTTP$协议版本</td></tr></tbody></table><ul><li><p>其余行称为首部行</p><ul><li>$Date$指示服务器发送响应报文的日期时间</li><li>$Server$指示服务器类型,类似于请求报文中的$User-agent$</li><li>$Last\enspace Modified$指示对象创建或最后修改的日期时间</li><li>$Content-Length$指示被发送对象的字节数</li><li>$Content-Type$指示实体体中对象为$HTML$文本</li></ul></li><li><p>实体体包含被请求的对象</p></li><li><p>$HTTP$响应报文的通用格式</p></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-2/Chp2-HTTP%E5%93%8D%E5%BA%94%E6%8A%A5%E6%96%87%E9%80%9A%E7%94%A8%E6%A0%BC%E5%BC%8F.png!blogimg" class="" width="400" title="HTTP响应报文通用格式"><h4 id="2-2-3-Cookie"><a href="#2-2-3-Cookie" class="headerlink" title="2.2.3 $Cookie$"></a>2.2.3 $Cookie$</h4><ul><li>$Cookie$的意义<ul><li>限制用户的访问</li><li>将内容与用户身份相关联</li><li>在无状态的$HTTP$上建立用户会话层</li></ul></li><li>$Cookie$的组成<ul><li>$HTTP$响应报文中的$Cookie$首部行</li><li>$HTTP$请求报文中的$Cookie$首部行</li><li>端系统中保留$Cookie$文件</li><li>$Web$服务器的$Cookie$数据库</li></ul></li><li>$Cookie$的使用<ul><li>客户向服务器发送<u>普通请求报文</u></li><li>服务器为客户创建$ID$如<code>U202073245</code>并放置在响应报文的首部行</li><li>客户存储$Cookie$</li><li>客户将$Cookie$放置在请求报文的首部行</li><li>服务器根据$Cookie$<strong>采取指定动作</strong>,并返回普通响应报文</li></ul></li></ul><h3 id="2-3-电子邮件"><a href="#2-3-电子邮件" class="headerlink" title="2.3 电子邮件"></a>2.3 电子邮件</h3><h4 id="2-3-1-电子邮件系统的构成"><a href="#2-3-1-电子邮件系统的构成" class="headerlink" title="2.3.1 电子邮件系统的构成"></a>2.3.1 电子邮件系统的构成</h4><ul><li>用户代理<ul><li>用户可以撰写编辑邮件、查看邮件</li><li>如网易邮箱大师、$FoxMail$</li></ul></li><li>邮件服务器<ul><li>存储用户邮件的服务器</li><li>在报文队列中维护需要发送的邮件报文</li></ul></li><li>简单邮件传输协议$SMTP$<ul><li>将邮件从发送方的客户端发送到发送方的邮件服务器</li><li>将邮件从发送方的邮件服务器发送到接收方的邮件服务器</li></ul></li></ul><h4 id="2-3-2-SMTP-协议"><a href="#2-3-2-SMTP-协议" class="headerlink" title="2.3.2 $SMTP$协议"></a>2.3.2 $SMTP$协议</h4><ul><li><p>基本介绍(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/smtp.php">在线交互程序</a>)</p><ul><li>使用$TCP$运输协议进行可靠的邮件传送,端口号为$25$</li><li>不使用中间邮件服务器发送邮件,而是<strong>直接在发送方服务器和接收方服务器间进行传输</strong></li><li>使用持续连接</li><li>要求报文(首部和信体)全部使用$ 7-bit\enspace ASCII$码</li><li>$SMTP$服务器用$CRLF.CRLF $表示邮件报文的结束</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-2/Chp2-SMTP%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="SMTP流程"></li><li><p>报文格式</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">From</span><span class="punctuation">: </span>alice@qq.com</span><br><span class="line"><span class="attribute">To</span><span class="punctuation">: </span>Bob@google.com</span><br><span class="line"><span class="attribute">Subject</span><span class="punctuation">: </span>Searching for the meaning of life.</span><br></pre></td></tr></table></figure></li><li><p>与$HTTP$协议的对比</p></li></ul><table><thead><tr><th align="center"></th><th align="center">$HTTP$</th><th align="center">$SMTP$</th></tr></thead><tbody><tr><td align="center">协议类型</td><td align="center">拉协议</td><td align="center">推协议</td></tr><tr><td align="center">报文编码格式</td><td align="center">不限制</td><td align="center">$ 7-bit\enspace ASCII$</td></tr><tr><td align="center">多对象</td><td align="center">每个对象分装在各自的响应报文中</td><td align="center">多个对象在多分部的报文中</td></tr></tbody></table><ul><li><p>使用$telnet$指令访问$QQ$邮箱</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></pre></td><td class="code"><pre><span class="line">telnet smtp.qq.com 25</span><br><span class="line">auth login</span><br><span class="line">base64-mail</span><br><span class="line">base64-authentication-code</span><br><span class="line">helo qq.com</span><br><span class="line">mail from:<xxx@xx.xx></span><br><span class="line">rcpt to:<xxx@xx.xx></span><br><span class="line">data</span><br><span class="line">Hello World!</span><br><span class="line">Hello World!</span><br><span class="line">.</span><br><span class="line">quit</span><br></pre></td></tr></table></figure></li></ul><h4 id="2-3-3-POP3-协议"><a href="#2-3-3-POP3-协议" class="headerlink" title="2.3.3 $POP3$协议"></a>2.3.3 $POP3$协议</h4><ul><li><p>运行在端口$110$的邮件访问协议</p></li><li><p>运行方式</p><ul><li><p>特许阶段:用户代理发送用户名和口令以鉴别用户</p><blockquote><p>主要命令:user <username>和pass <password></p><p>服务器的响应回答有$+OK$、$-ERR$</p></blockquote></li><li><p>事务处理阶段:用户代理取回报文,同时可以对报文做删除标记的更改,获取邮件统计信息</p><blockquote><p>$list$——列出报文号码</p><p>$retr$——用报文号码取回报文</p><p>$dele$——用报文号码删除邮件</p></blockquote></li><li><p>更新阶段</p><blockquote><p>$quit$——结束$POP3$会话,服务器删除被标记为删除的邮件</p></blockquote></li></ul></li><li><p>使用$telnet$指令访问$QQ$邮箱</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></pre></td><td class="code"><pre><span class="line">telnet pop.qq.com 110</span><br><span class="line">user QQ-ID</span><br><span class="line">pass authentication-code</span><br><span class="line">list</span><br><span class="line">retr 1</span><br><span class="line">dele 1</span><br><span class="line">quit</span><br></pre></td></tr></table></figure></li></ul><h4 id="2-3-4-IMAP-协议"><a href="#2-3-4-IMAP-协议" class="headerlink" title="2.3.4 $IMAP$协议"></a>2.3.4 $IMAP$协议</h4><ul><li><p>允许用户在服务器上组织自己的<strong>邮件目录</strong></p></li><li><p>维护$IMAP$会话的用户信息:目录名以及报文$ID$与目录名之间的映射关系</p></li><li><p>使用$telnet$指令访问$QQ$邮箱</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></pre></td><td class="code"><pre><span class="line">telnet imap.qq.com 143</span><br><span class="line">a01 login QQ-ID authentication-code</span><br><span class="line">a02 list "" *</span><br><span class="line">a03 select inbox</span><br><span class="line">a04 create folder</span><br><span class="line">a05 delete folder</span><br><span class="line">a06 rename oldfolder new folder</span><br></pre></td></tr></table></figure></li></ul><h3 id="2-4-DNS"><a href="#2-4-DNS" class="headerlink" title="2.4 $DNS$"></a>2.4 $DNS$</h3><h4 id="2-4-1-DNS-服务"><a href="#2-4-1-DNS-服务" class="headerlink" title="2.4.1 $DNS$服务"></a>2.4.1 $DNS$服务</h4><ul><li>$DNS$简况(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/dns.php">在线交互程序</a>)<ul><li>$DNS$能够将主机名解析为主机的$IP$地址</li><li>$DNS$是一个分布式数据库,由很多$DNS$服务器按照层次结构组织</li><li>$DNS$运行在端到端系统上,且使用$UDP$协议($53$号端口)进行报文传输</li></ul></li><li>$DNS$解析过程<ul><li>用户请求$URL$<code>http://cloudchewie.com/index.html</code></li><li>浏览器抽取出主机名<code>cloudchewie.com</code>并发送给$DNS$客户端</li><li>$DNS$客户端向$DNS$服务器发送查询请求报文</li><li>$DNS$服务器返回包含主机名对应$IP$地址的响应报文</li><li>$DNS$客户端将$IP$地址传送给浏览器</li><li>浏览器向$IP$地址所在$Web$服务器发起$TCP$连接</li></ul></li><li>其他服务<ul><li>主机别名:获取主机别名对应的主机规范名</li><li>邮件服务器别名:获取邮件服务器主机别名对应的主机规范名</li><li>负载分配:将一个$IP$地址<strong>集合</strong>与<strong>同一个规范主机名</strong>相联系</li></ul></li></ul><h4 id="2-4-2-DNS-工作机理"><a href="#2-4-2-DNS-工作机理" class="headerlink" title="2.4.2 $DNS$工作机理"></a>2.4.2 $DNS$工作机理</h4><ul><li><p>采用单台$DNS$服务器</p><ul><li>单点故障:一旦崩溃,因特网瘫痪</li><li>通信容量:该台服务器不得不处理所有$HTTP$请求报文和电子邮件报文</li><li>时延严重:集中式数据库将造成严重的拥塞与时延</li><li>难以维护:不得不持续更新以适应数据更改</li></ul></li><li><p>采用分布式层次化数据库</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-2/Chp2-DNS%E5%AE%9E%E7%8E%B0.png!blogimg" class="" width="400" title="DNS实现"><ul><li>根服务器提供$TLD$服务器的$IP$地址</li><li>$TLD$服务器提供权威服务器的$IP$地址,负责所有顶级域名和所有国家顶级域</li><li>权威服务器提供域名到$IP$地址的映射服务</li><li>本地$DNS$服务器(默认$DNS$服务器)<ul><li>当一台主机需要做一个域名查询的时候,查询请求首先被发送到本地域名服务器</li></ul></li></ul></li><li><p>递归查询与迭代查询(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/dns_query.php">在线交互程序</a>)</p></li><li><p>$DNS$缓存(<a href="https://gaia.cs.umass.edu/kurose_ross/interactive/DNS_HTTP_delay.php">在线交互程序</a>)</p><ul><li>一旦 (任何) 域名服务器得知了某个映射, 就将其缓存</li><li>在一定的时间间隔后缓存的条目将会过期(自动消除)</li><li>$TLD$服务器的地址通常被缓存在本地$DNS$服务器中,以减少根服务器负载</li></ul></li></ul><h4 id="2-4-3-DNS-记录"><a href="#2-4-3-DNS-记录" class="headerlink" title="2.4.3 $DNS$记录"></a>2.4.3 $DNS$记录</h4><p>格式为四元组$(Name,Value,Type,TTL)$,其中$TTL$表示记录的生存时间</p><table><thead><tr><th align="center">$Type$</th><th align="center">$Name$</th><th align="center">$Value$</th></tr></thead><tbody><tr><td align="center">$A$</td><td align="center">主机名</td><td align="center">$IP$地址</td></tr><tr><td align="center">$CNAME$</td><td align="center">主机别名</td><td align="center">规范主机名</td></tr><tr><td align="center">$NS$</td><td align="center">域</td><td align="center">该域权威域名服务器的<strong>主机名</strong></td></tr><tr><td align="center">$MX$</td><td align="center">邮件服务器的主机别名</td><td align="center">邮件服务器的规范主机名</td></tr></tbody></table><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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#使用nslookup进行DNS解析</span></span><br><span class="line"><span class="comment">#进入交互模式</span></span><br><span class="line">nslookup</span><br><span class="line">server www.net.cn</span><br><span class="line">server dns.hust.edu.cn</span><br><span class="line"><span class="built_in">set</span> ty=A</span><br><span class="line">cloudchewie.com</span><br><span class="line">hust.edu.cn</span><br><span class="line"><span class="built_in">set</span> ty=ns</span><br><span class="line">cloudchewie.com</span><br><span class="line"><span class="built_in">set</span> ty=cname</span><br><span class="line">cloudchewie.com</span><br><span class="line"><span class="built_in">set</span> ty=mx</span><br><span class="line">hust.edu.com</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机网络 </tag>
</tags>
</entry>
<entry>
<title>计算机网络与通信笔记(三)</title>
<link href="/posts/2022/11/12/notes/Computer-Network-and-Communication-3/"/>
<url>/posts/2022/11/12/notes/Computer-Network-and-Communication-3/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Computer-Network-and-Communication.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">计算机网络与通信笔记</div> <div class="tag-link-sitename"> 点击下载计算机网络与通信笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="三、运输层"><a href="#三、运输层" class="headerlink" title="三、运输层"></a>三、运输层</h2><h3 id="3-1-运输层服务"><a href="#3-1-运输层服务" class="headerlink" title="3.1 运输层服务"></a>3.1 运输层服务</h3><h4 id="3-1-1-基本概念"><a href="#3-1-1-基本概念" class="headerlink" title="3.1.1 基本概念"></a>3.1.1 基本概念</h4><ul><li>运输层为不同主机上运行的应用进程提供逻辑通信信道$(logical\enspace communication)$</li><li>发送方把应用数据划分为<strong>报文段</strong>,交给网络层;接收方把报文段重组成应用数据,交付给应用层</li><li>网络层协议<ul><li>网际协议$IP$是<strong>不可靠服务</strong>,它将<strong>尽力而为</strong>地在不同通信主机间交付报文段</li><li>但是它并不确保报文段的交付,不保证报文段按序交付,不保证报文段中数据完整性</li></ul></li><li>运输层是<strong>应用进程</strong>之间的逻辑通信,网络层是<strong>不同主机</strong>之间的逻辑通信</li><li>运输层将<strong>两个端系统间</strong>$IP$的交付服务扩展为在端系统上的<strong>两个进程之间</strong>的交付服务</li></ul><h4 id="3-1-2-基本模型"><a href="#3-1-2-基本模型" class="headerlink" title="3.1.2 基本模型"></a>3.1.2 基本模型</h4><ul><li>假设家庭$A$的$6$个孩子要与家庭$B$的$6$个孩子相互写信</li><li>家庭$A$中的$Amy$负责收集所有信件并投递到邮局,并且负责将家庭$B$寄来的所有信件送到每个人手中</li><li>家庭$B$中的$Bob$负责收集所有信件并投递到邮局,并且负责将家庭$A$寄来的所有信件送到每个人手中<ul><li>进程$= $孩子们</li><li>应用层报文$ = $信封中的信笺</li><li>主机$ = $家庭</li><li>运输层协议$ = $$Amy$和$Bob$</li><li>网络层协议$ = $邮局提供的服务</li></ul></li><li>$Amy$和$Bob$只在各自家里进行收发工作<ul><li>运输层协议只工作在端系统中</li></ul></li><li>假如$Amy$和$Bob$外出度假,分别替换成年龄较小的$Lisa$和$John$,他们可能会由于粗心大意丢失信件<ul><li>不同运输层协议提供的服务模型不一样,如$UDP$和$TCP$</li></ul></li><li>邮局不承诺信件送达的时间<ul><li>运输层协议能够提供的服务<strong>受到底层网络协议的服务模型的限制</strong></li></ul></li><li>邮局不承诺信件一定安全可靠的送达,可能在路上丢失,但$Amy$和$Bob$可在较长时间内没有受到对方的回信时,再次誊写信件并寄出<ul><li>在网络层不提供某些服务的情况下,运输层自己提供</li></ul></li></ul><h3 id="3-2-多路分解和多路复用"><a href="#3-2-多路分解和多路复用" class="headerlink" title="3.2 多路分解和多路复用"></a>3.2 多路分解和多路复用</h3><h4 id="3-2-1-基本概念"><a href="#3-2-1-基本概念" class="headerlink" title="3.2.1 基本概念"></a>3.2.1 基本概念</h4><ul><li><p>多路分解:将运输层报文段中的数据交付到正确的套接字的工作</p></li><li><p>多路复用:在源主机从不同套接字收集数据块,并为每个数据块封装首部信息(用于多路分解)生成报文段,然后将报文段传送到网络层</p></li></ul><h4 id="3-2-2-要求"><a href="#3-2-2-要求" class="headerlink" title="3.2.2 要求"></a>3.2.2 要求</h4><ul><li>套接字有唯一标识符</li><li>每个报文段<strong>有特殊字段指示该报文段要交付到的套接字</strong><ul><li>包括<strong>源端口号字段</strong>和<strong>目的端口号字段</strong>,各占$16\enspace bit$</li><li>其中$0\sim1023$范围的端口号称为周知端口号,保留给诸如$HTTP$、$SMTP$等周知应用层协议</li></ul></li></ul><h4 id="3-2-3-无连接的多路分解与多路复用"><a href="#3-2-3-无连接的多路分解与多路复用" class="headerlink" title="3.2.3 无连接的多路分解与多路复用"></a>3.2.3 无连接的多路分解与多路复用</h4><ul><li>一个$UDP$套接字是<strong>由一个<u>二元组</u>全面标识的</strong>,具体为<strong>(源端口号,目的端口号)</strong></li><li>如果两个报文段具有不同的$IP$地址或源端口号,但是具有相同的目的$IP$地址和目的端口号,那么这两个报文段将<strong>通过相同的目的套接字被定向到相同的目的进程</strong></li><li>具体过程<ul><li>创建套接字:运输层自动为其分配一个端口号$(1024\sim65535)$,或者指定一个端口号</li><li>假设主机$A$中的一个进程具有$UDP$端口$19157$,欲发送一个报文给位于主机$B$中的另一进程,其具有$UDP$端口$46428$</li><li>主机$A$中的运输层封装报文,添加源端口号$19157$,目的端口号$46428$以及其他两个字段得到报文段</li><li>运输层将报文段传递到网络层,网络层封装$IP$数据包并尽力而为地传送到接收主机</li><li>如果报文段到达主机$B$,主机$B$中的运输层即检查目的端口号$46428$,并将报文段交付给端口号为$46428$的套接字</li><li>当主机$B$需要回发报文段给$A$时,即需要<strong>将来自A的报文段中的源端口号作为目的端口号</strong>发送报文段</li></ul></li></ul><h4 id="3-2-4-面向连接的多路分解与多路复用"><a href="#3-2-4-面向连接的多路分解与多路复用" class="headerlink" title="3.2.4 面向连接的多路分解与多路复用"></a>3.2.4 面向连接的多路分解与多路复用</h4><ul><li><p>一个$TCP$是<strong>由一个<u>四元组</u>全面标识的</strong>,具体为(源$IP$地址,源端口号,目的$IP$地址,目的端口号)</p></li><li><p>与$UDP$协议不同,如果两个报文段具有不同的$IP$地址或源端口号,而且具有相同的目的$IP$地址和目的端口号,那么这两个报文段将<strong>通过不同的目的套接字被定向到不同的目的进程</strong></p></li><li><p>具体过程</p><ul><li>假设主机$A$中的一个进程具有$TCP$端口$19157$,欲发送一个报文给位于主机$B$中的另一进程,其具有$TCP$端口$46428$</li><li>同时主机$C$中的一个进程也具有$TCP$端口$19157$,也要发送报文给主机$B$中具有$TCP$端口$46428$的进程</li><li>主机$B$<strong>依然能够正确分解具有两个相同源端口号和目的端口号的连接</strong>,因为二者的$IP$地址不同,决定着其套接字不同</li></ul></li><li><p>$Web$服务器与$TCP$</p><ul><li><p>在$Web$服务器中,当客户发送请求时,所有报文段的目的端口号都将为$80$,这时服务器根据源$IP$地址和源端口号来区分来自不同客户的报文段</p></li><li><p>在当今的高性能服务器中,通常只使用一个进程,而是通过为不同的套接字创建<strong>新的线程(轻量级子进程)</strong>提供服务</p><blockquote><p>非持久$HTTP$对每一个请求都建立不同的套接字,会影响性能</p></blockquote></li></ul></li></ul><h3 id="3-3-UDP-协议"><a href="#3-3-UDP-协议" class="headerlink" title="3.3 $UDP$协议"></a>3.3 $UDP$协议</h3><h4 id="3-3-1-UDP-报文段格式"><a href="#3-3-1-UDP-报文段格式" class="headerlink" title="3.3.1 $UDP$报文段格式"></a>3.3.1 $UDP$报文段格式</h4><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-UDP%E6%8A%A5%E6%96%87%E6%AE%B5%E6%A0%BC%E5%BC%8F.png!blogimg" class="" width="400" title="UDP报文段格式"><ul><li><strong>长度字段</strong>指示了包括首部在内的报文段中<strong>字节</strong>数</li><li><strong>校验和</strong>用于接收方校验报文段是否发生差错</li></ul><h4 id="3-3-2-UDP-校验和"><a href="#3-3-2-UDP-校验和" class="headerlink" title="3.3.2 $UDP$校验和"></a>3.3.2 $UDP$校验和</h4><ul><li><p>发送方</p><ul><li><p>把报文段看作是<strong>16比特字的序列</strong></p></li><li><p>对报文段的所有$16$比特字的和进行<strong>反码</strong>运算,加法有溢出时,需要将进位加到末尾</p></li><li><p>将计算校验和的结果写入$UDP$校验和字段中</p></li></ul></li><li><p>接收方</p><ul><li>将<strong>包括校验和在内</strong>的所有$16$比特字加在一起</li><li>如果没有差错,结果将为$1111,1111,1111,1111$</li></ul></li><li><p>无法纠正错误</p></li></ul><h4 id="3-3-2-UDP-与-TCP-对比"><a href="#3-3-2-UDP-与-TCP-对比" class="headerlink" title="3.3.2 $UDP$与$TCP$对比"></a>3.3.2 $UDP$与$TCP$对比</h4><table><thead><tr><th align="center">$UDP$</th><th align="center">$TCP$</th><th align="center">说明</th></tr></thead><tbody><tr><td align="center">无需建立连接</td><td align="center">需要建立连接(握手)</td><td align="center">建立连接会增加时延</td></tr><tr><td align="center">无需维护连接状态</td><td align="center">需要维护连接状态</td><td align="center"></td></tr><tr><td align="center">首部$8\enspace Byte$</td><td align="center">首部$20\enspace Byte$</td><td align="center">$UDP$段首部开销较小</td></tr><tr><td align="center">无拥塞控制,可按需随时发送</td><td align="center">有拥塞控制</td><td align="center">大量使用$UDP$会导致路由器中堆积分组,挤占$TCP$会话</td></tr><tr><td align="center">适用于$DNS$服务、因特网电话</td><td align="center">适用于$Web$服务、电子邮件</td><td align="center"></td></tr></tbody></table><h3 id="3-4-可靠数据传输"><a href="#3-4-可靠数据传输" class="headerlink" title="3.4 可靠数据传输"></a>3.4 可靠数据传输</h3><h4 id="3-4-1-可靠数据传输概述"><a href="#3-4-1-可靠数据传输概述" class="headerlink" title="3.4.1 可靠数据传输概述"></a>3.4.1 可靠数据传输概述</h4><ul><li><p>可靠数据传输:数据<strong>不会丢失</strong>且数据<strong>不会出错</strong></p></li><li><p><strong>网络层的IP协议是不可靠信道</strong>,因此为了实现可靠数据传输,<strong>需要在运输层做出保证可靠数据传输的工作</strong></p></li><li><p>可靠数据传输协议模型</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-RDT%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="RDT模型"></li><li><p>使用有限状态机$FSM$描述发送方和接收方</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-FSM%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="FSM模型"><ul><li>在模型中,存在多个状态</li><li>事件引起状态的变化</li><li>状态变化过程中又会存在一系列动作的发生,以完成状态的转换</li></ul></li></ul><h4 id="3-4-2-可靠信道上的可靠传输"><a href="#3-4-2-可靠信道上的可靠传输" class="headerlink" title="3.4.2 可靠信道上的可靠传输"></a>3.4.2 可靠信道上的可靠传输</h4><ul><li><p>信道模型:不会发生比特传输错误,不会造成分组丢失</p></li><li><p>有限状态机$FSM$</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-RDT1.0.png!blogimg" class="" width="400" title="RDT1.0"><ul><li>发送方——发送分组:发送报文段,继续等待上层调用发送分组</li></ul></li><li><p>接收方——接收分组:分解得到数据</p></li></ul><h4 id="3-4-3-简单停等协议"><a href="#3-4-3-简单停等协议" class="headerlink" title="3.4.3 简单停等协议"></a>3.4.3 简单停等协议</h4><ul><li><p>信道模型:分组比特可能受损,但分组将按序接收,不会丢失</p></li><li><p>停止等待协议:每次发送分组后,发送方需要等待接收方的反馈</p></li><li><p>设想方案</p><ul><li>第一步:判断分组受损——<strong>差错检测</strong></li><li>第二步:通知发送方分组是否受损——<strong>接收方反馈(ACK与NAK)</strong></li><li>第三步:得知分组受损$(NAK)$后,发送方的处理手段——<strong>出错重传</strong></li></ul></li><li><p>有限状态机$FSM$</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-RDT2.0.png!blogimg" class="" width="400" title="RDT2.0"><ul><li>发送方<ul><li>发送分组:生成校验码$checksum$,用于进行差错检测,发送报文段,跳转到等待状态</li><li>反馈$ACK$:清除缓冲区中的报文段,跳转到等待上层调用发送分组的状态</li><li>反馈$NAK$:重新发送报文段(保存在缓冲区中),继续等待反馈</li></ul></li><li>接收方<ul><li>接收到的分组出错$(corrupt)$:生成$NAK$报文段,发送给发送方</li><li>接收到的分组正确$(notcorrupt)$:分解得到数据,生成$ACK$报文段,发送给发送方</li></ul></li></ul></li><li><p>新的问题:$ACK$和$NAK$分组也可能受损</p><ul><li>受损的分组视为$ACK$不合适</li><li>受损的分组视为$NAK$可能导致接收方重复收到分组——<strong>对分组进行编号,便于接收方识别是新分组还是旧分组</strong></li></ul></li><li><p>改进后的有限状态机$FSM$</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-RDT2.1.png!blogimg" class="" width="400" title="RDT2.1"><ul><li><p>取消$NAK$</p><ul><li>取消$NAK$,接收方对最后一个正确收到的分组发送$ACK$</li><li>同时,$ACK$中必须指出被确认分组的序号,以便于接收方标识正确收到了哪一个分组</li><li>$Double\enspace ACK=NAK$</li></ul></li><li><p>取消$NAK$后的有限状态机$FSM$</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-RDT2.2.png!blogimg" class="" width="400" title="RDT2.2"></li></ul></li></ul><h4 id="3-4-4-实用停等协议"><a href="#3-4-4-实用停等协议" class="headerlink" title="3.4.4 实用停等协议"></a>3.4.4 实用停等协议</h4><ul><li><p>信道模型:分组比特可能受损,还可能会丢包</p></li><li><p>如何检测丢包:耐心的等待——<strong>超时重传</strong></p></li><li><p>有限状态机$FSM$</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-RDT3.0.png!blogimg" class="" width="400" title="RDT3.0"></li><li><p>性能低下</p><ul><li><strong>信道利用率:传输时间/总时间(从发送方开始传输到发送方接收到反馈所用的时间)</strong></li><li>假设某条$1Gbps$的链路,存在$15ms$的端到端时延,则传送$1KB$大小的分组所用的时间为$T_{trans}+RTT=\frac{1KB}{1Gbps}+15ms\times2=30.008ms$,也即链路速度被限制为$33KB/sec$的吞吐量</li><li>网络协议大大限制了物理资源的利用率,实际上用于传输分组的时间只占$\frac{0.008}{30.008}=0.00027$</li></ul></li></ul><h4 id="3-4-5-滑动窗口协议"><a href="#3-4-5-滑动窗口协议" class="headerlink" title="3.4.5 滑动窗口协议"></a>3.4.5 滑动窗口协议</h4><ul><li><p>解决方案:允许发送方发送多个分组后再等待确认</p><ul><li>必须增大序号范围</li><li>发送方和接收方需要对分组进行缓存</li></ul></li><li><p>流水线技术工作原理</p><ul><li><p>用$k$位进行序号编码</p></li><li><p>扩大发送方乃至接收方的缓冲区大小——<strong>窗口</strong></p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-%E7%BC%93%E5%86%B2%E5%8C%BA%E7%A4%BA%E6%84%8F%E5%9B%BE.png!blogimg" class="" width="400" title="缓冲区示意图"></li><li><p>当丢失一个分组后,如何进行重传</p></li></ul></li></ul><table><thead><tr><th align="center"></th><th align="center">$GBN(Go\enspace Back\enspace N)$协议</th><th align="center">选择重传$(SR)$协议</th></tr></thead><tbody><tr><td align="center">确认方式</td><td align="center">累计确认</td><td align="center">单个确认</td></tr><tr><td align="center">定时器</td><td align="center">对所有已发送但未确认的分组统一设置定时器</td><td align="center">对所有已发送但未确认的分组分别设置定时器</td></tr><tr><td align="center">超时$(n)$</td><td align="center">重传分组$n$和窗口中所有序号大于$n$的分组</td><td align="center">仅重传分组$n$</td></tr><tr><td align="center">失序分组</td><td align="center">丢弃(不缓存),接收方缓冲区大小为$1$个分组</td><td align="center">缓存,接收方缓冲区大小与发送方一致</td></tr><tr><td align="center">失序分组处理</td><td align="center">重发按序到达的最高序号分组的$ACK$</td><td align="center">对失序分组进行选择性确认</td></tr><tr><td align="center">要求</td><td align="center">$W_s\le2^k-1,W_r=1$</td><td align="center">$W_s,W_r\le2^{k-1}$</td></tr></tbody></table><ul><li><p>分组序号长度与窗口大小的关系</p><ul><li>假设分组序号为$k$位,发送窗口大小为$W_s$,接收窗口大小为$W_r$,发送窗口当前序号为$i,\cdots,i+W_{s-1}$</li><li>极端情况下,发送的所有分组均正常接收,但是$ACK$均丢失,则<ul><li>发送窗口当前序号为$i,\cdots,i+W_{s-1}$</li><li>接收窗口当前序号为$i+W_s,\cdots,(i+W_s+W_{r-1})\enspace mod\enspace 2^k$</li><li>两个窗口序号不重复,且均在$[0,2^k-1]$内</li></ul></li><li>要求$W_s+W_r\le2^k$</li></ul></li></ul><h3 id="3-5-TCP-协议"><a href="#3-5-TCP-协议" class="headerlink" title="3.5 $TCP$协议"></a>3.5 $TCP$协议</h3><h4 id="3-5-1-TCP-工作流程"><a href="#3-5-1-TCP-工作流程" class="headerlink" title="3.5.1 $TCP$工作流程"></a>3.5.1 $TCP$工作流程</h4><ul><li>建立连接——三次握手<ul><li>客户发送一个特殊的$TCP$报文段</li><li>服务器也发送一个特殊的$TCP$报文段作为响应</li><li>客户再发送一个特殊的报文段作为响应,可以承载有效负荷</li></ul></li><li>发送数据<ul><li>客户进程从套接字传递出数据流,经过该门后即进入$TCP$控制</li><li>$TCP$将数据流引导至该连接的<strong>发送缓存</strong>中</li><li>$TCP$每次取出一块数据放入报文段中进行运输,每次取出的数据数量受限于<strong>最大报文段长度</strong>$MSS$,而该值受限于<strong>最大链路层帧长度</strong>$MTU$(能从源到目的地的所有链路上发送的最大链路层帧)</li><li>$MSS$指报文段中应用层数据的最大长度,而不包含$TCP$首部</li></ul></li><li>$TCP$是<strong>全双工</strong>的</li></ul><h4 id="3-5-2-TCP-报文"><a href="#3-5-2-TCP-报文" class="headerlink" title="3.5.2 $TCP$报文"></a>3.5.2 $TCP$报文</h4><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-3/Chp3-TCP%E6%8A%A5%E6%96%87%E9%A6%96%E9%83%A8%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="TCP报文首部结构"><ul><li><p>序号:<strong>报文段的首个字节在整个字节流中的的序号</strong>,是离散型编号</p><blockquote><p>假设数据流包含$500000$字节,$MSS$为$1000$字节,那么将构建$500$个报文段,其序号依次为$0$、$1000$、$2000$等等</p></blockquote></li><li><p>确认号(期待号):期待得到的下一个字节的序号,当$ACK$字段为$1$时,确认号才有效</p><blockquote><p>如确认号为$n+1$时表示接收方接收到第$n$号,期望得到$n+1$号分组,这称为<strong>累计确认</strong></p></blockquote></li><li><p>首部长度:$TCP$最长$60$个字节,以$4$个字节为单位进行递进,即与图示行数相同,取值为$[5,15]$</p></li><li><p>窗口:用于$TCP$流量控制</p></li><li><p>检验和:差错检测</p></li><li><p>紧急指针:当$URG$字段为$1$时,表示需要传送紧急数据;紧急指针指示了紧急数据的结尾</p><blockquote><p>当$PSH$字段为$1$时,整个数据部分都为紧急数据</p></blockquote></li></ul><h4 id="3-5-3-TCP-机制"><a href="#3-5-3-TCP-机制" class="headerlink" title="3.5.3 $TCP$机制"></a>3.5.3 $TCP$机制</h4><ul><li><p>快速重传</p><ul><li>原因:超时周期往往太长,增加重发丢失分组的延时</li><li>通过重复的$ACK$检测丢失报文段:如果发送收到一个数据的$3$个<strong>冗余</strong>$ACK$,即确认数据之后的报文段丢失,以在超时到来之前重传报文段</li><li>每次$TCP$重传都会将下一次超时间隔设置为先前值的两倍</li></ul></li><li><p>流量控制</p><ul><li><p>可变滑动窗口:接收方将缓冲区的空闲空间大小写入报文段返回给发送方,发送方根据空闲空间大小调整发送窗口大小</p></li><li><p>当发送方接收到空闲空间大小为$0$时,为避免进入假死状态(发送方不发送报文段,接收方不发送反馈),发送方会持续发送小的报文段试探接收方</p></li><li><p>当接收方重新获得空间空间后,小报文段将会被接收方响应处理并反馈空闲空间大小给发送方</p></li></ul></li><li><p>拥塞控制</p><ul><li><p>拥塞:包括<strong>丢包</strong> (路由器缓冲区溢出)和<strong>时延长</strong> (在路由器缓冲区排队)</p><ul><li>当路由器缓存无限大且不会重传时,随着发送方速率的增大,接收方速率也会增大;但当接收方速率饱和时,吞吐量也随之固定。而此时,也就意味着在路由器缓冲区排队的分组越来越多,造成越来越大的排队时延</li><li>当路由器缓存有限且会对丢失的分组重传时,对延迟到达(而非丢失)的分组的重传使得发送方速率比理想情况下更大于接收方速率;发送方在遇到大时延时所进行的不必要重传会引起路由器转发不必要的分组拷贝而占用其链路带宽</li><li>当路由器缓存有限且会进行超时重传时,由于超时重传,当分组被丢弃时,该分组曾用到的所有<strong>上游</strong>传输容量被浪费</li></ul></li><li><p>端到端拥塞控制</p><ul><li>每个发送方自动感知网络拥塞的程度,发送方根据感知的结果限制外发的流量</li><li>如何限制外发流量:控制拥塞窗口长度</li><li>如何感知拥塞程度:超时或者$3$个冗余$ACK$</li><li>如何调节发送速率:加性增,乘性减(出现丢包事件后将当前$ CongWin $大小减半,可以大大减少注入到网络中的分组数;当没有丢包事件发生,每个$RTT$之后将$CongWin$增大$1$个$MSS$,使拥塞窗口缓慢增大,防止网络过早拥塞)</li></ul></li><li><p>$Reno$算法</p><ul><li>慢启动:$ CongWin = 1 MSS$,小于阈值$ssthresh$时指数增加</li><li>拥塞避免:达到$ssthresh$后,加性增</li><li>收到$3$个冗余$ACK$:$ssthresh=CongWin/2$,$CongWin=ssthresh+3MSS$,加性增</li><li>超时:$ssthresh=CongWin/2$,$CongWin=1MSS$,指数增加到阈值后加性增</li></ul></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机网络 </tag>
</tags>
</entry>
<entry>
<title>计算机网络与通信笔记(四)</title>
<link href="/posts/2022/11/12/notes/Computer-Network-and-Communication-4/"/>
<url>/posts/2022/11/12/notes/Computer-Network-and-Communication-4/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Computer-Network-and-Communication.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">计算机网络与通信笔记</div> <div class="tag-link-sitename"> 点击下载计算机网络与通信笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="四、网络层"><a href="#四、网络层" class="headerlink" title="四、网络层"></a>四、网络层</h2><h3 id="4-1-网络层概述"><a href="#4-1-网络层概述" class="headerlink" title="4.1 网络层概述"></a>4.1 网络层概述</h3><ul><li>网络层:实现<strong>主机和主机</strong>之间的通信<ul><li>数据平面:如何转发<ul><li><strong>转发</strong>是指将分组从一个输入链路接口转移到适当的输出链路接口的路由器本地动作</li><li>转发的时间尺度很短$(ns)$,因此采用硬件实现</li><li>假设某个驾驶员从武昌到汉口经过许多立交桥,<strong>转发类比于经过某个立交桥的过程</strong></li><li>分组交换机上的网络层根据转发表以及分组首部信息,将分组向适当链路进行转发</li></ul></li><li>控制平面:如何选路<ul><li><strong>路由选择</strong>是指确定分组从源到目的地所采取的端到端路径的网络范围处理过程</li><li>路由选择的时间尺度较长$(s)$,因此采用软件实现</li><li>假设某个驾驶员要从武昌到汉口,<strong>路由选择类比于规划从武昌到汉口的行程</strong></li><li>在全局范围为主机之间的通信进行选路,选路结果反映为分组交换机上的转发表</li></ul></li></ul></li></ul><h3 id="4-2-IP-协议"><a href="#4-2-IP-协议" class="headerlink" title="4.2 $IP$协议"></a>4.2 $IP$协议</h3><h4 id="4-2-1-IPv4-数据报格式"><a href="#4-2-1-IPv4-数据报格式" class="headerlink" title="4.2.1 $IPv4$数据报格式"></a>4.2.1 $IPv4$数据报格式</h4><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-IPv4%E6%95%B0%E6%8D%AE%E6%8A%A5%E6%A0%BC%E5%BC%8F.png!blogimg" class="" width="400" title="IPv4数据报格式"><ul><li><p>版本:$4\enspace bit$,规定数据报的$IP$协议版本</p></li><li><p>首部长度:由于首部中含有可选字段,因此需要使用首部长度确定载荷实际开始的地方</p></li><li><p>服务类型:$8\enspace bit$,代表报文处理方式,每一位分别代表最小延时、最大吞吐量、最高可靠性、最小成本,<strong>只选一个</strong></p></li><li><p>数据报长度:<strong>首部加上载荷的总长度</strong>,以字节计,理论上数据报最大长度为$65535$字节,但以太网链路报文只允许$1500$字节</p></li><li><p>标识、标志、片偏移:与$IP$分片有关,但$IPv6$不允许在路由器上分组分片</p><ul><li>链路层帧大小有限,超过时应该进行分片处理</li><li>将一个大数据报拆分为几个小数据报,传输后在目的主机进行重组</li><li>标识:$16\enspace bit$,网络层服务的上层传输层的同一次报文(可能超过$1500$字节),使用相同标记</li><li>标志:$3bit$,$1$位保留,$2$位表示是否能分片,$3$位($MF$)表示分片是否结束($1$为未结束,$0$为结束)</li><li>片偏移:每个分片在整个报文(分组)中的位置(<strong>以8字节为度量单位</strong>)</li></ul></li><li><p>生存时间:确保数据报不会永远在网络中循环,每当一台路由器处理数据包时,该字段值减$1$,减为$0$时必须丢弃该数据报</p></li><li><p>协议:当数据报到达最终目的地时才有用,指示了载荷要交付给哪个特定的运输层协议,如$6$标识$TCP$,$17$标识$UDP$</p><blockquote><p>数据报中的协议号将网络层与运输层绑定在一起,报文段中的端口号将运输层和应用层绑定在一起</p></blockquote></li><li><p>首部校验和:按<strong>首部</strong>的每两个字节进行校验和计算,每台路由器都要重新计算首部校验和并放置在首部校验和位中</p></li><li><p>源和目的地址</p></li><li><p>如果不包含可选字段,$IP$数据段的首部将包含$20$个字节</p><blockquote><p>首部长度不定($20-60$字节),中间节点(路由器)需要消耗相当资源用于分组处理</p></blockquote></li></ul><h4 id="4-2-2-IP-地址"><a href="#4-2-2-IP-地址" class="headerlink" title="4.2.2 $IP$地址"></a>4.2.2 $IP$地址</h4><ul><li><p>$IP$地址的定义</p><ul><li>一台主机通常只有一条链路连接到网络;当主机中的$IP$想发送数据报时,通过该链路发送</li><li>主机与物理链路之间的边界叫做<strong>接口</strong></li><li>路由器负责从链路上接收数据报并从其他链路转发出去,其与任意一条与其连接的链路之间的边界也叫做<strong>接口</strong></li><li>每条主机和路由器都能发送和接收数据报,因此$IP$协议要求每台主机和路由器接口都有自己的$IP$地址</li><li><strong>一个IP地址和一个接口相关联,而不是与包含该接口的主机或路由器相关联</strong></li></ul></li><li><p>$IPv4$编址</p><ul><li>每个$IP$地址长度为$32$比特,因此共有$2^{32}$个可能的$IP$地址</li><li>通常按照点分十进制记法书写,即地址中的每个字节用十进制表示,各字节间以句点隔开,如$193.32.216.9$</li><li>地址分类</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-IPv4%E5%9C%B0%E5%9D%80%E5%88%86%E7%B1%BB.png!blogimg" class="" width="400" title="IPv4地址分类"><ul><li>大约有$40$亿$IPv4$地址,需要分类以便于寻址和层次化构造网络</li><li>在同一个局域网上的主机或路由器的$IP $地址中的网络号必须一样</li><li>路由器具有两个及以上的$ IP $地址,其每一个接口都有一个不同网络号的$ IP $地址</li><li>$A$类地址以$0-127$开头,$B$类地址以$128-191$开头,$C$类地址以$192-224$开头</li></ul></li><li><p>特殊$IP$地址</p><ul><li>全$0$主机号的$IP$地址表示网络本身,如$129.152.0.0$是网络号为$129.152$的$B$类网络</li><li>全$1$主机号的$IP$地址表示广播地址,如$129.152.255.255$是网络号为$129.152$的$B$类网络的广播地址</li><li>十进制$127$开头的地址是回环地址,用于测试自身$TCP$或$IP$软件是否正常</li></ul></li></ul><h4 id="4-2-3-子网划分"><a href="#4-2-3-子网划分" class="headerlink" title="4.2.3 子网划分"></a>4.2.3 子网划分</h4><ul><li><p>子网的定义</p><ul><li>划分子网以方便地址划分、分块管理,需要<strong>从主机号中借用一部分比特作为子网号</strong></li><li><strong>网络号</strong>唯一指定主机所在网络,该网络包含若干子网</li><li><strong>子网号</strong>唯一指定主机所在子网</li><li><strong>主机号</strong>唯一指定子网内某台主机</li></ul></li><li><p>子网掩码</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-%E5%AD%90%E7%BD%91%E6%8E%A9%E7%A0%81.png!blogimg" class="" width="400" title="子网掩码"><ul><li>将网络号和子网号相应的位置全置$1$,主机号相应的位置全置$0$,得到子网掩码</li><li>将$IP$地址和子网掩码相与得到网络地址</li><li>$A$、$B$、$C$类地址具有默认子网掩码</li></ul></li></ul><table><thead><tr><th align="center">地址分类</th><th align="center">默认掩码</th><th align="center">网络号比特</th><th align="center">主机号比特</th><th align="center">子网可容纳主机数</th></tr></thead><tbody><tr><td align="center">A</td><td align="center">255.0.0.0</td><td align="center">8</td><td align="center">24</td><td align="center">2^24-2</td></tr><tr><td align="center">B</td><td align="center">255.255.0.0</td><td align="center">16</td><td align="center">16</td><td align="center">2^16-2</td></tr><tr><td align="center">C</td><td align="center">255.255.255.0</td><td align="center">24</td><td align="center">8</td><td align="center">2^8-2</td></tr></tbody></table><ul><li><p>子网寻址:先检查目的$IP$地址的网络号,然后检查目的$IP$地址的子网号</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-%E5%AD%90%E7%BD%91%E5%AF%BB%E5%9D%801.png!blogimg" class="" width="400" title="子网寻址1"><ul><li><p>当主机$1$访问主机$2$时,先检查主机$2$是否在本网络上:将目的主机地址$128.30.33.138$与本网络子网掩码$255.255.255.128$相与得到$128.30.33.128$不等于子网$1$的网络地址$128.30.33.0$,说明主机$2$不在主机$1$的子网中</p></li><li><p>依次查询路由器$R_1$的路由转发表</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-%E5%AD%90%E7%BD%91%E5%AF%BB%E5%9D%802.png!blogimg" class="" width="400" title="子网寻址2"><ul><li>对于第一条记录,目的主机地址与子网掩码相与得到$128.30.33.128$不等于$128.30.33.0$</li><li>对于第二条记录,目的主机地址与子网掩码相与得到$128.30.33.128$等于该记录的目的网络地址,因此要访问目的主机时需要访问目的网络地址,也即经过路由器$R_1$的接口$1$进行转发</li></ul></li></ul></li><li><p>子网划分</p><ol><li>确定子网个数</li><li>确定每个子网内的最大主机数</li><li>确定从主机号字段中借用的比特数,用于创建子网号字段</li><li>确定主机号字段中保留的比特数</li><li>确定原始网络号字段和主机号字段的比特数</li><li>检查主机号字段长度大于第3步和第4步中确定的比特数</li><li>设置子网号字段的最佳长度</li><li>创建子网掩码</li><li>确定有效的子网号</li><li>确定每个子网的$IP$地址范围</li></ol></li></ul><h4 id="4-2-4-无类域间路由-CIDR"><a href="#4-2-4-无类域间路由-CIDR" class="headerlink" title="4.2.4 无类域间路由$CIDR$"></a>4.2.4 无类域间路由$CIDR$</h4><ul><li><p>编码格式</p><ul><li>$IP$地址表示为{网络前缀,主机号}</li><li>斜线记法:$192.168.0.1/25$表示左边$25$位为网络前缀,对应子网掩码表示中的$192.168.0.1/255.255.255.128$</li><li>简写记法:$10.0.0.0/10$简写为$10/10$</li></ul></li><li><p>示例</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-CIDR%E7%BC%96%E5%9D%80.png!blogimg" class="" width="400" title="CIDR编址"><ul><li>假设某个地址块为$XX.XX.XX.XX/n$,即该地址块的左边$n$位为网络前缀,则剩余$32-n$位用来具体表示主机号,其地址数为$2^{32-n}$</li><li>这个$ ISP $共有$ 64 $个$ C $类网络。如果不采用$ CIDR $技术,则在与该$ ISP $的路由器交换路由信息的每一个路由器的路由表中,就需要有$ 64 $个项目。但采用地址聚合后,只需用路由聚合后的$ 1 $个项目$ 206.0.64.0/18 $就能找到该$ ISP$。</li></ul></li><li><p>寻址方式</p><ul><li><p>使用$CIDR$编址后,路由转发表变为如下格式</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-CIDR%E8%B7%AF%E7%94%B1%E8%BD%AC%E5%8F%91%E8%A1%A8.png!blogimg" class="" width="400" title="CIDR路由转发表"></li><li><p>最长前缀匹配</p><ul><li>使用$ CIDR $时,路由表中的每个项目由<strong>网络前缀</strong>和<strong>下一跳地址</strong>组成</li><li>在查找路由表时可能会得到不止一个匹配结果,此时从匹配结果中选择具有最长网络前缀的路由</li><li>网络前缀越长,其地址块就越小,因而路由就越具体</li></ul><blockquote><p>如对于实例中,假设目的地址为$206.0.71.142$,其匹配大学的地址块前缀$206.0.68.0/22$,也匹配四系的地址块前缀$206.0.71.128/25$,根据最长前缀匹配规则,需要匹配四系的地址块</p></blockquote></li><li><p>示例(B、A、E、F、C、D)</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-CIDR%E8%B7%AF%E7%94%B1%E8%BD%AC%E5%8F%91%E7%A4%BA%E4%BE%8B.png!blogimg" class="" width="400" title="CIDR路由转发示例"></li></ul></li></ul><h4 id="4-2-5-网络地址转换-NAT"><a href="#4-2-5-网络地址转换-NAT" class="headerlink" title="4.2.5 网络地址转换$NAT$"></a>4.2.5 网络地址转换$NAT$</h4><ul><li><p>实现原理</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-NAT%E8%BD%AC%E6%8D%A2.png!blogimg" class="" width="400" title="NAT转换"><ul><li><p>发送数据报:将每个向外发送报文的源$IP$地址与端口号<strong>映射</strong>为$NAT\enspace IP$地址以及新端口号;</p></li><li><p>同时远程客户机或者服务器将以$NAT\enspace IP$地址以及新端口号做为目的地址进行响应</p></li><li><p>$NAT$路由器将每一个地址转换对记录在$NAT$转换表中</p><blockquote><p> 转换表的表项为:(源$IP$地址,端口号)$\rightarrow$($NAT$的$IP$地址,新端口号)</p></blockquote></li><li><p>接收数据报:根据$NAT$转换表将每个向内发送报文的$NAT\enspace IP$地址和端口号替换为相应的源$IP$地址以及端口号</p></li><li><p>内网主机对外网不可见;$ISP$变更后内网地址无需变化</p></li></ul></li><li><p>使用$NAT$的变化</p></li></ul><table><thead><tr><th align="center">不使用$NAT$</th><th align="center">使用$NAT$</th></tr></thead><tbody><tr><td align="center">主机=$IP$</td><td align="center">主机=$IP$+端口</td></tr><tr><td align="center">统一全球地址</td><td align="center">全球地址+本地地址</td></tr><tr><td align="center">主机访问双向公平</td><td align="center">外网主机无法主动访问内网主机</td></tr></tbody></table><h4 id="4-2-6-IPv6-数据报格式"><a href="#4-2-6-IPv6-数据报格式" class="headerlink" title="4.2.6 $IPv6$数据报格式"></a>4.2.6 $IPv6$数据报格式</h4><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-IPv6%E6%95%B0%E6%8D%AE%E6%8A%A5%E6%A0%BC%E5%BC%8F.png!blogimg" class="" width="400" title="IPv6数据报格式"><ul><li><p>由于$IPv4$地址池即将用尽,因此为了适应对大$IP$地址空间的需求,开发了新的$IP$协议——$IPv6$协议</p></li><li><p>$IP$地址长度被扩大为$128$比特,首部长度为固定为$40$字节</p></li><li><p>版本:标识$IP$版本号,该字段值为$6$时即表示$IPv6$协议</p><blockquote><p>注意:该字段值置为$4$并不能创建合法的$IPv4$数据报</p></blockquote></li><li><p>流量类型:同$IPv4$中的$TOS$字段</p></li><li><p>流标签:用于标识一条数据报的流</p></li><li><p>有效载荷长度:无符号整数,指示数据的字节数</p></li><li><p>下一个首部:标识需要交给哪个运输层协议,如$UDP$或$TCP$</p></li><li><p>跳限制:同$IPv4$中的生存时间</p></li><li><p>不存在分片与重组:中间结点不再负责分片和重组,由端结点负责</p><blockquote><p>即不允许在中间路由器上进行分片与重组,只能在源和目的地执行;当路由器接收到的$IPv6$数据报太大时,会丢弃数据报并发回<u>分组太大</u>的$ICMP$差错报文</p></blockquote></li><li><p>不存在首部校验和:中间节点无需计算</p></li><li><p>不存在选项字段:首部长度固定,加速中间节点转发速度</p></li><li><p>从$IPv4$到$IPv6$</p><ul><li><p>双栈技术</p><ul><li>新加入的设备支持$IPv4/IPv6$双协议栈</li><li>一段链路上,如果源和目标均支持$IPv6$,则使用$IPv6$进行通信</li><li>如果任一方不支持$IPv6$,则使用$IPv4$进行通信</li><li>转换开销较大,可能会出现信息的丢失</li></ul></li><li><p>隧道技术</p><ul><li>将$IPv6$的数据报封装在$IPv4$的数据报中,即建立隧道传输$IPv6$数据报</li></ul></li></ul></li></ul><h3 id="4-3-路由器的工作原理"><a href="#4-3-路由器的工作原理" class="headerlink" title="4.3 路由器的工作原理"></a>4.3 路由器的工作原理</h3><h4 id="4-3-1-路由器结构"><a href="#4-3-1-路由器结构" class="headerlink" title="4.3.1 路由器结构"></a>4.3.1 路由器结构</h4><ul><li><p>路由器基本结构</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-%E8%B7%AF%E7%94%B1%E5%99%A8%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="路由器体系结构"><ul><li><p>输入端口:执行物理层的线路连接功能;执行数据链路层功能;执行网络层查找功能决定输出端口</p></li><li><p>交换结构:根据输入端口查找得到的输出端口,将数据转发到对应的输出端口</p></li><li><p>输出端口:存储从交换结构接收的分组,并执行对应的链路层和物理层功能</p></li><li><p>路由选择处理器:执行<strong>控制平面</strong>功能</p></li></ul></li><li><p>转发方式:如何选择输出端口</p><ul><li>基于目的地转发:根据输入分组的最终目的地转发,类比于在立交桥中根据目的地决定出口</li><li>通用转发:除了目的地,还根据其他因素进行转发</li></ul></li></ul><h4 id="4-3-2-输入端口"><a href="#4-3-2-输入端口" class="headerlink" title="4.3.2 输入端口"></a>4.3.2 输入端口</h4><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-%E8%B7%AF%E7%94%B1%E5%99%A8%E8%BE%93%E5%85%A5%E7%AB%AF%E5%8F%A3.png!blogimg" class="" width="400" title="路由器输入端口"><ul><li><p>基于目的地转发</p><ul><li><p>对于$32$位的$IP$地址,在转发表中可以维护每个目的地址的表项,但需要维护的表项数十分庞大</p></li><li><p>实际上,可以通过将目的地址进行分组管理,通过<strong>前缀匹配</strong>的方式进行转发</p></li><li><p>当有多个匹配项时,采用<strong>最长前缀匹配</strong>规则:寻找表中最长的匹配项</p><blockquote><p>例如有如下仅包含$4$个表项的转发表</p><table><thead><tr><th align="center">前缀匹配</th><th align="center">输出接口</th></tr></thead><tbody><tr><td align="center">$1100,1000,0001,0111,0001,0$</td><td align="center">$0$</td></tr><tr><td align="center">$1100,1000,0001,0111,0001,1000$</td><td align="center">$1$</td></tr><tr><td align="center">$1100,1000,0001,0111,0001,1$</td><td align="center">$2$</td></tr><tr><td align="center">其他</td><td align="center">$3$</td></tr></tbody></table><p>对于目的地址是$1100,1000,0001,0111,0001,0110,1010,0001$的分组,其匹配转发表中的第一项,因此将被转发到输出接口$0$;对于目的地址是$1100,1000,0001,0111,0001,1000,1010,0001$的分组,其匹配转发表中的第二项和第三项,但根据最长前缀匹配规则,将被转发到输出接口$1$</p></blockquote></li></ul></li></ul><h4 id="4-3-3-交换结构"><a href="#4-3-3-交换结构" class="headerlink" title="4.3.3 交换结构"></a>4.3.3 交换结构</h4><ul><li><p>内存交换</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-%E5%86%85%E5%AD%98%E4%BA%A4%E6%8D%A2.png!blogimg" class="" width="400" title="内存交换"><ul><li>输入和输出端口间的交换是在<strong>路由处理器</strong>的直接控制下完成</li><li>分组被拷贝到系统内存中,在$CPU$的控制下转发至输出端口</li><li>转发速度受限于内存带宽(每个分组走两次总线)</li></ul></li><li><p>总线交换</p><ul><li>输入报文经共享总线将分组直接转发到输出端口</li><li>总线交换速度受限于总线带宽</li></ul></li><li><p>内联网络交换</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-%E5%86%85%E8%81%94%E7%BD%91%E7%BB%9C%E4%BA%A4%E6%8D%A2.png!blogimg" class="" width="400" title="内联网络交换"><ul><li>克服总线带宽限制</li></ul></li></ul><h4 id="4-3-4-输出端口"><a href="#4-3-4-输出端口" class="headerlink" title="4.3.4 输出端口"></a>4.3.4 输出端口</h4><ul><li><p>排队现象的产生</p><ul><li>设输入线路和输出线路的传输速率相同,均为$R_{line}\enspace pkt/s$,有$N$个输入输出端口,交换结构传送速率为$R_{switch}$</li><li>当$R_{switch}\gg R_{line}$时,可以使得输入的分组<strong>无时延</strong>地通过交换结构</li></ul></li><li><p>输入排队</p><ul><li>当$R_{switch}$不满足$R_{switch}\gg R_{line}$时,将使得输入端口出现分组排队</li><li>线头阻塞:输入队列中排队分组被位于线头的另一个分组阻塞,须等待交换结构发送<ul><li>当两个不同输入端口的分组均要发往同一个输出端口时,其中一个分组必须等待交换结构转发完毕另一个分组</li></ul></li></ul></li><li><p>输出排队</p><ul><li><p>当$R_{switch}$超过$R_{line}$时,需要对分组进行缓存</p></li><li><p>输出端口缓冲区溢出会导致分组的排队和丢失</p></li><li><p>缓冲区大小:对于有$N$条$TCP$连接经过的链路而言,缓存数量为$B=RTT\frac{R}{\sqrt{N}}$</p><blockquote><p>其中$R$为链路容量,$RTT$为平均往返延迟</p></blockquote></li></ul></li></ul><h4 id="※4-4-7-ICMP-协议"><a href="#※4-4-7-ICMP-协议" class="headerlink" title="※4.4.7 $ICMP$协议"></a>※4.4.7 $ICMP$协议</h4><ul><li>用途</li><li>分类</li><li>报文格式</li><li>应用<ul><li>$Ping$命令</li><li>$Tracert$命令</li></ul></li></ul><h3 id="4-5-路由协议"><a href="#4-5-路由协议" class="headerlink" title="4.5 路由协议"></a>4.5 路由协议</h3><h4 id="4-5-1-概述"><a href="#4-5-1-概述" class="headerlink" title="4.5.1 概述"></a>4.5.1 概述</h4><ul><li><p>什么是路由</p><ul><li>路由是从源主机到目的主机的路径</li><li>路由是在路由器上执行的过程,包括接收路由协议、对路由进行选路</li><li>路由是在两个路由器间传递消息的路由协议</li><li>路由是在路由协议的处理下得到的路由表</li></ul></li><li><p>什么是好的路由</p><ul><li>好的路径:无环路,可收敛,费用低</li><li>好的路由协议:开销低(内存、带宽占用),安全性高</li><li>好的路由表:保存局部路由,共同构成完成路由</li></ul></li><li><p>什么是路由器</p><ul><li><p>默认路由器:一台主机连接到的路由器</p></li><li><p>源路由器:源主机的默认路由器</p></li><li><p>目的路由器:目标主机的默认路由器</p></li><li><p>给定一组路由器以及连接路由器的链路,从中找到一条从源路由器到目标路由器的好路径</p><blockquote><p>例外:$A$和$B$之间的路径费用很低,但是二者处于两个组织之间,而这两个组织作出的路由策略不允许相互通行</p></blockquote></li></ul></li><li><p>路由抽象模型</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-4/Chp4-%E8%B7%AF%E7%94%B1%E6%8A%BD%E8%B1%A1%E5%9B%BE%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="路由抽象图模型"><ul><li><p>该模型表示为图$G(N,M)$</p></li><li><p>其中路由器集$N={u,v,w,x,y,z}$,链路集$M={(u,v),(u,x), (v,x), (v,w), (x,w), (x,y), (w,y), (w,z), (y,z)}$</p></li><li><p>$c(x,x’)$为节点$X$和$X’$间边的费用,例如图中$c(w,z)=5$</p></li></ul><blockquote><p> 费用可以是经济的,但也有可能和链路的带宽以及链路拥塞状况有关</p></blockquote><ul><li><p>路径费用$ (x_1, x_2, x_3,\cdots, x_p) = c(x_1,x_2) + c(x_2,x_3) + \cdots + c(x_{p-1},x_p) $</p></li><li><p>选路算路就是选取路径费用最低的路径</p></li></ul></li><li><p>选路算法分类</p><ul><li><p>按照性能目标分类</p><ul><li><p>链路状态算法——基于路径成本最优</p><ul><li>所有路由器都知道整个网络拓扑图以及链路的费用信息</li></ul></li><li><p>距离向量算法——基于路径距离最短</p><ul><li>每个路由器仅有与其相连链路的费用信息,通过迭代计算过程与相邻节点交换信息</li><li>路由信息可以更快地变化,可以响应拓扑或链路费用的变化</li></ul></li></ul></li><li><p>按照负载是否敏感分类</p><ul><li>负载敏感算法:链路费用会动态地变化以反映出链路的当前状况</li><li>负载迟钝算法:链路费用不明显地反映链路的当前状况</li></ul></li></ul></li></ul><h4 id="4-5-2-链路状态选择算法-LS-与-OSPF"><a href="#4-5-2-链路状态选择算法-LS-与-OSPF" class="headerlink" title="4.5.2 链路状态选择算法$LS$与$OSPF$"></a>4.5.2 链路状态选择算法$LS$与$OSPF$</h4><ul><li><p>迪杰斯特拉算法</p><ul><li>算法概述<ul><li>给定带权图$G=\langle V,E,W\rangle$(所有边的权重为正值)和源路由器$s$,找到源路由器$s$到所有其他路由器$t$的最小成本$\delta(s,t)$和最小成本路径$\langle s,\dots,t\rangle$</li><li>算法从结点集$V-S$中选择当前最小成本路径估计最小的路由器$u$,将$u$从$Q$中删除,并加入到$S$中,$u.d$就是源路由器$s$到$u$的最短路径的长度。这里$Q$是一个最小优先队列,保存结点集$V-S$</li><li>以每个结点为源路由器,由上述算法得到的最小生成树即可得到<strong>源路由器到其他路由器的转发表</strong></li></ul></li><li>前提:所有节点都知道网络拓扑和链路费用<ul><li>所有节点具有该网络的同一个完整的视图</li><li>通过链路状态广播获得信息</li></ul></li><li>目标:产生某节点的转发表<ul><li>计算从某节点到网络中所有其它节点的最低费用,并产生转发表</li></ul></li><li>算法复杂度为$O(n^2)$</li><li>算法的问题<ul><li>当模型采用负载流量作为路径成本,即模型为负载敏感型网络时,会产生振荡问题</li><li>解决方案<ul><li>不以负载流量作为成本——无法解决高拥塞问题</li><li>所有的路由器不同时运行$LS$算法</li></ul></li></ul></li></ul></li><li><p>$OSPF$协议</p><ul><li><p>即$Open\enspace Shortest\enspace Path\enspace First$协议,公开发表的最短路径优先协议</p></li><li><p>协议交互范围:工作在本自治系统域内,采用<strong>泛洪法</strong>发送消息</p></li><li><p>协议交互消息内容:<strong>与本路由器相邻的所有路由器的链路状态</strong></p></li><li><p>协议交互时机:<strong>仅当链路状态发生变化时</strong>,采用泛洪法向所有路由器发送信息</p></li><li><p>当链路状态发生变化时,每个路由器都向本$AS$中的所有路由器发送与本路由器相邻的所有路由器的链路状态,信息发送完毕后,所有路由器上都将有全网一致的拓扑结构图</p><blockquote><p>即使链路状态未发生变化,每$30$分钟广播一次链路状态</p><p>链路状态以$OSPF$通告的形式封装在报文中,由$IP$分组承载(协议号:$89$)</p><p>$OSPF$路由器之间的交换经过$MD5$鉴别,以确认$OSPF$通告的真实性,防止伪造和篡改</p></blockquote></li></ul></li></ul><h4 id="4-5-3-距离向量选择算法-DV-与-RIP"><a href="#4-5-3-距离向量选择算法-DV-与-RIP" class="headerlink" title="4.5.3 距离向量选择算法$DV$与$RIP$"></a>4.5.3 距离向量选择算法$DV$与$RIP$</h4><ul><li>距离向量选择:$d_x(y)=min_v{c(x,v)+d_v(y)}$</li><li>$RIP$路由表更新算法<ul><li>路由器$X$得到相邻路由器$Y$的路由表,从而得知$Y$到网络$Z$的最短距离为$N$</li><li>如果路由器$X$没有到网络$Z$的路由条目,则添加一条经由路由器$Y$到网络$Z$距离$N+1$的路由条目</li><li>如果路由器$X$已有到网络$Z$的路由条目,其距离为$M$,如果$M>N+1$,则更新该条目为经由路由器$Y$到网络$Z$距离$N+1$,否则不更新</li></ul></li><li>当链路状态改变时<ul><li>在$ t0 $时刻,$y $检测到链路费用变化,更新距离向量,同时将这个变化通知给它的邻居</li><li>在$ t1$时刻,$ z $收到来自$ y $的更新报文并更新距离向量表,计算出到$x$的新的最低费用,并向邻居发送它的新距离向量</li><li>在$ t2$时刻,$y $收到自$z$的更新并更新其距离向量表,$Y$的最低费用未变,因此$y$不发送任何报文给$z$</li></ul></li><li>协议参数<ul><li>链路费用:相邻两点链路费用为$1$跳,最大费用限制为$15$</li><li>通告周期:选路更新通告周期为$30$秒</li><li>邻居离线:邻居离线判定周期为$180$秒</li><li>协议端口:基于$UDP$,端口为$520$</li></ul></li></ul><h4 id="4-5-5-域间-BGP-4-协议"><a href="#4-5-5-域间-BGP-4-协议" class="headerlink" title="4.5.5 域间$BGP-4$协议"></a>4.5.5 域间$BGP-4$协议</h4><ul><li>层次路由<ul><li>因特网规模过大,导致路由器无法存储每台主机的选路信息,路由表更新的报文广播将导致无剩余带宽供发送数据使用</li><li>将路由器聚合到一个区域,即自治系统$AS$,在不同$AS$内的路由器可以运行不同的自治系统内部选路协议</li></ul></li><li>转发选路算法<ul><li>转发表是由$AS$内部选路算法和$AS$间选路算法共同决定的</li><li>$AS$内部选路算法为内部目的地址设置转发表信息</li><li>$AS$内部选路算法和$AS$间选路算法共同为外部目的地址设置转发表信息</li><li>假设从源到目标仅有一条路可选<ul><li>假设$AS1$从$AS$间选路协议知道子网$ x $经过网关路由器$1c$至$AS3$可达,但是通过$AS2$不可达</li><li>$AS1$向它的所有路由器广播该可达信息</li><li>路由器$1d $知道,它的接口$ I $在到路由器$1c$的最低费用路径上</li><li>从而路由器$1d$将表项$ (x,I)$放入其转发表</li></ul></li><li>假设从源到目标有多条路径可选<ul><li>现在假设$AS1$知道子网$ x $可以通过$ AS3 $和$ AS2$到达</li><li>为了配置转发表, 路由器$ 1d $必须决定通过哪个网关路由器转发报文($1b$或$1c$)</li><li>热土豆选路: 将报文发送到最近的路由器</li></ul></li></ul></li><li>$BGP-4$发言人<ul><li>每一个$AS$要选择一个路由器作为该$AS$的$BGP$发言人</li><li>两个$ BGP $发言人通过一个共享网络连接在一起</li><li>$BGP$发言人通过$BGP$通告广播该自治系统$AS$能够到达哪些网络<ul><li>通过将多个路由前缀聚合为单一前缀并转发之达到路由通告的目的</li><li>发言人得知某些通告信息后,向该$AS$内的路由器广播,路由器为之创建新的表项</li><li>路由通告中包含前缀(能够到达的网络前缀)、路径(到达前缀地址经过的路径)、下一跳(到达前缀地址需要经过的下一跳地址)</li></ul></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机网络 </tag>
</tags>
</entry>
<entry>
<title>计算机网络与通信笔记(五)</title>
<link href="/posts/2022/11/12/notes/Computer-Network-and-Communication-5/"/>
<url>/posts/2022/11/12/notes/Computer-Network-and-Communication-5/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Computer-Network-and-Communication.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">计算机网络与通信笔记</div> <div class="tag-link-sitename"> 点击下载计算机网络与通信笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="五、链路层"><a href="#五、链路层" class="headerlink" title="五、链路层"></a>五、链路层</h2><h3 id="5-1-概述"><a href="#5-1-概述" class="headerlink" title="5.1 概述"></a>5.1 概述</h3><h4 id="5-1-1-术语"><a href="#5-1-1-术语" class="headerlink" title="5.1.1 术语"></a>5.1.1 术语</h4><ul><li><p>主要讨论传播时延:从输出链路的起点到目的地传播所需的时间</p></li><li><p>节点:主机和路由器</p></li><li><p>链路:沿着通信路径连接相邻节点的通信信道</p></li><li><p>帧:数据链路层的分组单元</p><blockquote><p>链路层负责将数据报封装成<strong>帧</strong>通过链路从一个节点传输到<strong>物理上相邻</strong>的下一个节点</p></blockquote></li></ul><h4 id="5-1-2-链路层基本模型"><a href="#5-1-2-链路层基本模型" class="headerlink" title="5.1.2 链路层基本模型"></a>5.1.2 链路层基本模型</h4><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-5/Chp5-%E9%93%BE%E8%B7%AF%E5%B1%82%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="链路层模型"><ul><li><p>数据报在不同链路上可能<strong>由不同的链路层协议</strong>进行处理</p><blockquote><p>第一段链路上由$PPP$处理,最后一段链路上由以太网处理,中间链路上由广域链路层协议处理</p></blockquote></li><li><p>不同的链路层协议可能提供不同的服务</p></li><li><p>链路层提供的服务</p><ul><li>成帧、链路访问:将数据加上头部和尾部,封装成数据帧,其中帧头部用$MAC$地址标识源和目的地</li><li>可靠传递:用于误码率高的链路,如无线链路</li><li>流量控制:在相邻的收发节点间限制流量</li><li>差错检测:接收方检测到错误存在后,给发送方发送信号要求重传或丢弃该数据帧</li><li>差错纠正:接收方检测和纠正帧中错误,不用重传</li><li>半双工和全双工:半双工时,链路两端的节点都能传输分组,但不能同时传输</li></ul></li><li><p>链路层的实现</p><ul><li>链路层在“适配器”(网卡$NIC$)或者芯片上实现</li></ul></li></ul><h4 id="5-1-3-差错检测和纠正"><a href="#5-1-3-差错检测和纠正" class="headerlink" title="5.1.3 差错检测和纠正"></a>5.1.3 差错检测和纠正</h4><ul><li>单比特奇偶校验:检测单个比特错误</li><li>二维奇偶校验:检测和纠正单个比特错误</li><li>因特网检查和:用于$TCP$、$UDP$和$IPv4$协议中</li><li>$CRC$冗余校验:广泛应用于以太网、$802.11\enspace WiFi$、$ATM$</li></ul><h3 id="5-2-多路访问链路和协议"><a href="#5-2-多路访问链路和协议" class="headerlink" title="5.2 多路访问链路和协议"></a>5.2 多路访问链路和协议</h3><h4 id="5-2-1-链路概述"><a href="#5-2-1-链路概述" class="headerlink" title="5.2.1 链路概述"></a>5.2.1 链路概述</h4><ul><li><p>链路类型</p><ul><li>点到点链路:$PPP$/以太网交换机和主机之间的点到点链路</li><li>广播链路(共享线路或介质):传统以太网/$802.11$无线$LAN$</li></ul></li><li><p>链路特点</p><ul><li><p>单个共享广播信道</p></li><li><p>两个或多个节点同时传输时,会相互干扰</p><blockquote><p>碰撞:一个节点同时收到两个或多个信号</p></blockquote></li></ul></li></ul><h4 id="5-2-2-信道划分协议"><a href="#5-2-2-信道划分协议" class="headerlink" title="5.2.2 信道划分协议"></a>5.2.2 信道划分协议</h4><ul><li><p>协议概述</p><ul><li>信道划分协议:将信道划分成小的“片”(时隙、频率、编码)分配给节点使用</li></ul></li><li><p>$TDMA(Time\enspace Division\enspace Multiple\enspace Access)$</p><ul><li>循环访问信道</li><li>每个节点在每次循环中得到固定长度的时隙(时隙长度=传输单个分组时间)</li><li>没有数据发送的时隙空闲</li></ul></li><li><p>$FDMA(Frequence\enspace Division\enspace Multiple\enspace Access)$</p><ul><li>信道按频谱分成若干频段</li><li>每个节点分配固定频段</li><li>在频段不用时该部分信道被闲置和浪费</li></ul></li><li><p>$CDMA(Code\enspace Division\enspace Multiple\enspace Access)$</p><ul><li>每个用户使用自己的码片序列对数据编码</li><li>当需要发送比特$1$时,发送$mbit$码片序列</li><li>当需要发送比特$0$时,发送$mbit$码片序列的二进制反码</li></ul></li></ul><h4 id="5-2-3-随机访问协议"><a href="#5-2-3-随机访问协议" class="headerlink" title="5.2.3 随机访问协议"></a>5.2.3 随机访问协议</h4><ul><li>协议概述<ul><li>当节点有数据发送时,以信道全部速率$R$传输,没有主节点起协调作用,因此两个或多个节点发送时会发生<strong>碰撞</strong></li><li>如何检测碰撞</li><li>如何从碰撞中恢复,如延时后重传</li></ul></li><li>$ALOHA(Additive\enspace Link\enspace On-Line\enspace HAwaii\enspace system)$</li><li>时隙$ALOHA$</li><li>载波监听$CSMA$</li><li>带冲突检测的载波侦听$CSMA/CD$</li></ul><h4 id="5-2-4-轮流协议"><a href="#5-2-4-轮流协议" class="headerlink" title="5.2.4 轮流协议"></a>5.2.4 轮流协议</h4><ul><li><p>协议概述</p><ul><li>信道划分协议在<strong>低负荷</strong>时效率低——即使只有一个活动节点,也只能分配到$\frac{1}{N}$的带宽</li><li>随机访问协议在<strong>高负荷</strong>时效率低——碰撞的开销增加</li></ul></li><li><p>轮询协议</p></li><li><p>令牌传递协议</p></li></ul><h3 id="5-3-交换局域网"><a href="#5-3-交换局域网" class="headerlink" title="5.3 交换局域网"></a>5.3 交换局域网</h3><h4 id="5-3-1-MAC-地址"><a href="#5-3-1-MAC-地址" class="headerlink" title="5.3.1 $MAC$地址"></a>5.3.1 $MAC$地址</h4><ul><li>又称为$LAN$地址、物理地址</li><li>$48$比特,前$24$比特由$IEEE$分配管理——$OUI$号,后$24$比特由厂商自行分配</li><li>通常采用<strong>十六进制</strong>表示法,如$5C-66-AB-90-75-B1$</li><li>在数据链路层标识<strong>每块网络适配器</strong>,使得能够在广播信道上寻址目标节点</li><li>$MAC$地址烧入网络适配器的$ROM$中,<strong>不可更改</strong></li><li>$MAC$地址类似于身份证号,不会随着网络迁移而改变;$IP$类似于邮件通信地址,需要根据网络配置策略更改</li><li>和网络层地址类似,主机和路由器上的每个接口(适配器)也都有链路层地址,但是链路层交换机的接口没有链路层地址</li><li>链路层交换机的作用是在主机和路由器之间承载数据报,并<strong>透明</strong>地执行该任务</li></ul><h4 id="5-3-2-地址解析协议-ARP"><a href="#5-3-2-地址解析协议-ARP" class="headerlink" title="5.3.2 地址解析协议$ARP$"></a>5.3.2 地址解析协议$ARP$</h4><ul><li><p>协议概述</p><ul><li>根据目标的$IP$地址获取其$MAC$地址</li><li>每台主机或路由器上存在$ARP$表,包含从$IP$地址到$MAC$地址的映射关系,具体存储为$\langle IP,MAC,TTL\rangle$</li><li>$ARP$协议工作在网络层和链路层之间</li></ul></li><li><p>同一局域网内工作流程</p><ul><li><p>建立$ARP$请求包</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-5/Chp5-%E5%B1%80%E5%9F%9F%E7%BD%91%E5%86%85ARP%E8%AF%B7%E6%B1%82%E5%8C%85.png!blogimg" class="" width="400" title="局域网内ARP请求包"></li><li><p>广播$ARP$请求包</p></li><li><p>建立$ARP$应答包</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-5/Chp5-%E5%B1%80%E5%9F%9F%E7%BD%91%E5%86%85ARP%E5%BA%94%E7%AD%94%E5%8C%85.png!blogimg" class="" width="400" title="局域网内ARP应答包"><ul><li><p>局域网内的所有适配器都把帧中的$ARP$分组向上传递给$ARP$模块</p></li><li><p>与目的$IP$地址匹配的适配器构建应答包</p></li><li><p>向源发送应答包</p></li><li><ul><li>源更新$ARP$表</li></ul></li></ul></li></ul></li><li><p>局域网间工作流程</p><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-5/Chp5-%E5%B1%80%E5%9F%9F%E7%BD%91%E9%97%B4ARP.png!blogimg" class="" width="400" title="局域网间ARP"><ul><li>发送方主机首先获取两个局域网间路由器的端口的$MAC$地址(ARP)</li><li>发送方向路由器发送帧</li><li>路由器根据路由转发表转发数据报到输出接口</li><li>输出接口将数据报发送给其适配器</li><li>适配器封装称新的帧,发送至另一个子网,此时的$MAC$地址即为目的主机地址</li></ul></li></ul><h3 id="5-4-以太网"><a href="#5-4-以太网" class="headerlink" title="5.4 以太网"></a>5.4 以太网</h3><h4 id="5-4-1-以太网帧结构"><a href="#5-4-1-以太网帧结构" class="headerlink" title="5.4.1 以太网帧结构"></a>5.4.1 以太网帧结构</h4><img src="https://picbed.cloudchewie.com/blog/post/Computer-Network-and-Communication-5/Chp5-%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%B8%A7%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="以太网帧结构"><ul><li><p>数据最长$1500$个字节</p></li><li><p>前同步码:总共$8$字节</p><ul><li><p>前$7$字节用于唤醒接收适配器,并同步时钟</p></li><li><p>前$7$字节为$10101010$,最后一个字节为$10101011$</p></li></ul></li><li><p>地址:$6$字节,若适配器收到以太网帧,目的地址为自己的$MAC$地址或广播地址(如$ARP$包),就将帧中的数据传给网络层,否则丢弃该帧</p></li><li><p>类型:上层协议类型(大多为$IP$协议,也支持其它协议,如$AppleTalk$)</p></li><li><p>$CRC$:由接收方检查,若检测到错误,就将该帧丢弃</p></li><li><p>以太网提供的服务</p><ul><li>无连接的服务:在发送适配器和接收适配器之间不需要握手</li><li>不可靠的服务:接收适配器<strong>不发送确认帧或否认帧</strong>给发送方</li></ul></li></ul><h4 id="5-4-2-CSMA-x2F-CD"><a href="#5-4-2-CSMA-x2F-CD" class="headerlink" title="5.4.2 $CSMA/CD$"></a>5.4.2 $CSMA/CD$</h4><ul><li><p>特点</p><ul><li>没有时隙</li><li>当适配器侦听到其它适配器在传输,则它不传输帧,即<strong>载波侦听</strong></li><li>正在传输的适配器若检测到其它适配器也在传输,则它中止自己的传输,即<strong>碰撞检测</strong></li><li>在重新传输之前,适配器要等待一段随机时间,即<strong>随机回退</strong></li></ul></li><li><p>术语</p><ul><li><p>拥塞信号:长度为$48$比特,用来确保所有传输者都能检测到碰撞而传输的信号</p></li><li><p>比特时间:传输$1$比特所需时间</p><blockquote><p> 在$10Mbps$的以太网中,当$K=1023$时,等待时间大约为$50ms$</p></blockquote></li></ul></li><li><p>算法</p><ul><li><p>适配器收到来自网络层的数据报,创建帧</p></li><li><p>若适配器检测到信道空闲,则开始传输帧;若检测到信道忙,就开始等待,直到信道空闲再开始传输该帧</p></li><li><p>若适配器传输了整个帧而没有检测到其它适配器的传输,则该适配器完成该帧的传输</p></li><li><p>若适配器在传输时检测到其它适配器也在传输,则停止传输,发送拥塞信号</p></li><li><p>中止传输后,适配器进入指数回退阶段,在经历第$m$次碰撞后,适配器随机从${0,1,2,\cdots,2^m-1}$中选择$K$值。适配器在等待$ K*512$比特时间后,返回第$2$步</p></li></ul></li><li><p><strong>指数回退算法</strong></p><ul><li>目的:适配器重传时试图估计正确的负载<ul><li>重载:随机等待的时间可能会更长</li></ul></li><li>第一次碰撞后:从${0,1}$中选择$K$;延迟是$K*512$比特传输时间</li><li>第二次碰撞后:从${0,1,2, 3}$中选择$K$</li><li>第十次碰撞后:从${0,1,2,3,4,…,1023}$中选择$K$</li></ul></li><li><p><strong>只能进行半双工通信</strong></p></li></ul><h4 id="5-4-3-争用期"><a href="#5-4-3-争用期" class="headerlink" title="5.4.3 争用期"></a>5.4.3 争用期</h4><ul><li>什么是争用期<ul><li>$A$向$B$发送数据,$\tau$后到达$B$(端到端传播时延,记为$τ$)</li><li>若$B$在$A$的数据到达之前,发送自己的数据(这时$B$检测信道是空闲的,因为它没收到任何数据),则必然会与$A$的数据在信道中发生碰撞</li><li>$A$在发送后多久才能知道发生碰撞?要等到$B$的数据到达$A$,而$A$还未结束发送</li><li>假设一个极端情况,当图中所示的时间差$\delta$($\delta$为$B$发送数据时刻与$A$数据到达$B$的时刻的时间差)趋向于$0$,则$A$检测到发生碰撞的时间$2\tau-\delta= 2\tau$</li><li>当超过这个时间都未检测到碰撞,则$A$发送出的数据就一定不会产生碰撞了。</li><li>以太网中端到端的往返时间$2\tau$称为争用期,也叫碰撞时间</li><li>只有经过争用期这段时间还未检测到碰撞,才能肯定这次发送不会发生碰撞,这时就可以放心把这一帧发送完毕。</li></ul></li><li>传统以太网($10Mbps$)规定争用期为$51.2μs$,最短有效帧长为$64$字节</li><li>最短有效帧长=$2τ*$链路传输速率</li><li>当传送前$64$个字节内没有发生碰撞时,就一直占用信道直到传输完毕所有字节(此时有其他数据来到时必须等待)</li><li>如果发生碰撞,则一定是在发送的前$64$字节之内</li><li>任何小于$64$字节的帧都是由于冲突而异常中止的无效帧</li></ul><h4 id="5-4-4-以太网交换机"><a href="#5-4-4-以太网交换机" class="headerlink" title="5.4.4 以太网交换机"></a>5.4.4 以太网交换机</h4><ul><li><p>链路层设备,负责存储转发以太网帧</p></li><li><p>主机不知道交换机的存在</p></li><li><p>检查帧头部,根据目的$MAC$地址转发</p></li><li><p>交换机的工作原理</p><ul><li>交换机不转发同一网段内通信的帧</li><li>当收到帧的目的地$MAC$地址属于另一个网段,则通过交换表决定向何端口转发</li><li>类比于物流中转站,从不同的物流点接受包裹(帧),当物流中转站发现包裹没有问题(帧无差错)时,保留包裹(缓存帧),否则丢弃包裹但不会要求商家重发包裹(<strong>不会要求帧重发</strong>),而由运输层处理丢包问题。物流中转站根据包裹发往的地址决定转到什么物流(交换机转发),并且不会修改包裹的寄件人地址(交换机不修改帧的源地址)</li></ul></li><li><p>交换机转发和过滤</p><ul><li>过滤:决定一个帧应该转发到某个接口还是丢弃帧</li><li>转发:决定一个帧应该被导向到哪个接口,并将其移动到那些接口</li><li>转发和过滤借助<strong>交换机表</strong>完成,其表项为<$MAC$地址,通向该地址的交换机接口,表项放在表中的时间></li></ul></li><li><p>交换机自学习</p><ul><li>交换机表初始为空</li><li>对于在每个接口接收到的每个入帧,存储一个表项</li><li>根据表项存储时间和老化期清除表项</li></ul></li><li><p>与路由器对比</p></li></ul><table><thead><tr><th align="center"></th><th align="center">路由器</th><th align="center">以太网交换机</th><th align="center">集线器</th></tr></thead><tbody><tr><td align="center">类型</td><td align="center">网络层设备</td><td align="center">链路层设备</td><td align="center">链路层设备</td></tr><tr><td align="center">维护</td><td align="center">维护路由表</td><td align="center">维护交换表</td><td align="center"></td></tr><tr><td align="center">算法</td><td align="center">路由算法</td><td align="center">MAC地址过滤、学习算法</td><td align="center"></td></tr><tr><td align="center">是否需配置</td><td align="center">需要配置</td><td align="center">即插即用</td><td align="center">即插即用</td></tr></tbody></table><h2 id="六、无线网络"><a href="#六、无线网络" class="headerlink" title="六、无线网络"></a>六、无线网络</h2><h3 id="6-1-概述"><a href="#6-1-概述" class="headerlink" title="6.1 概述"></a>6.1 概述</h3><ul><li>特性<ul><li>无线特性:基于无线链路</li><li>移动特性:用户的网络接入点变化</li></ul></li><li>包含固定基础设施的网络组成<ul><li>无线主机——手机</li><li>无线链路——大气层<ul><li>信号强度递减</li><li>会受到来自其他源的干扰</li><li>多径传播</li><li>比特差错率高于有线网络<ul><li>采用$CRC$进行校验</li><li>采用$ARQ$协议重传</li></ul></li></ul></li><li>基站——连接无线网络,负责转发覆盖范围内的主机的分组,起到链路层中继作用<ul><li>关联:主机在某个基站的覆盖范围内</li><li>切换:主机从某个基站切换到另一个基站</li></ul></li><li>基础设施——预先建立的固定基站</li></ul></li><li>$Ad\enspace hoc$网络<ul><li>不包含固定基础设施的自组网络——无基站</li><li>每个移动主机兼具主机和基站的作用</li><li>节点(移动主机)仅仅能够在其覆盖范围内向其他节点传送数据</li><li>节点之间相互通信组成的临时网络:在它们内部进行选路和地址分配</li></ul></li><li>无线链路的质量<ul><li>信噪比$SNR(SIGNAL-NOISE\enspace RATIO)$:信号强度与噪声强度的比值</li><li>比特差错率$BER(Bit\enspace Error\enspace Rate)$</li><li>调制方案相同,$SNR$越高,$BER$越低;$SNR$相同 ,比特传输率高的调制方案的$BER$高</li></ul></li></ul><h3 id="6-2-WiFi-概述"><a href="#6-2-WiFi-概述" class="headerlink" title="6.2 $WiFi$概述"></a>6.2 $WiFi$概述</h3><ul><li>执行$802.11$协议的无线$LAN$</li><li>$802.11$协议是一个协议簇,使用$CSMA/CA$协议进行多路访问<ul><li>$802.11a$的频率范围为$5.1\sim5.8GHz$</li><li>$802.11ac$的频率范围为$5.1\sim5.8GHz$,支持单流和多流通信</li><li>$802.11b$的频率范围为$2.4\sim2.485GHz$,不需要许可证</li><li>$802.11g$的频率范围为$2.4\sim2.485GHz$</li><li>$802.11n$的频率范围为$2.4\sim2.485GHz$和$5.1\sim5.8GHz$,支持单流和多流通信</li></ul></li><li>$802.11b$的信道划分<ul><li>将$85MHz$划分为$11$个部分重叠的信道,仅当两个信道间隔$4$个及以上的信道时没有重叠,可以同时工作,如$1、6、11$</li><li>每个无线访问接入点$AP$周期性发送信标帧,包含自己的$SSID$和$MAC$</li><li>主机扫描$11$个信道获取所有可用的$AP$的信标帧</li><li>主机连接到某个$AP$,加入其子网,并通过$dhcp$获取$IP$地址(需要身份鉴别)</li></ul></li><li>发送流程<ul><li>监听到信道闲置$DIFS$秒后才开始传输帧,并且不进行冲突检测</li><li>监听到信道忙后,则定时避退,定时到且信道闲置就发送数据</li><li>接收方收到帧后,等待$SIFS$秒发送$ACK$</li><li>发送方收到确认后,继续发送数据;没有收到确认则重新发送</li></ul></li><li>冲突避免<ul><li>发送方在发送帧之前,使用$CSMA$协议发送短的请求$RTS$帧给$AP$($RTS$也可能冲突)预约信道</li><li>$AP$回应允许发送$CTS$帧表示预约成功</li><li>其他发送方也能接收到$RTS$帧,收到后推迟自己的发送</li><li>如果$RTS$发生冲突,则两个发送方进行随即回退,总有一方先发送第二个$RTS$帧</li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机网络 </tag>
</tags>
</entry>
<entry>
<title>算法设计与分析笔记(一)</title>
<link href="/posts/2022/08/21/notes/Algorithm-Design-and-Analysis-1/"/>
<url>/posts/2022/08/21/notes/Algorithm-Design-and-Analysis-1/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Algorithm-Design-and-Analysis.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">算法设计与分析笔记</div> <div class="tag-link-sitename"> 点击下载算法设计与分析笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="一、算法基础"><a href="#一、算法基础" class="headerlink" title="一、算法基础"></a>一、算法基础</h2><h3 id="1-循环不变量的证明"><a href="#1-循环不变量的证明" class="headerlink" title="1.循环不变量的证明"></a>1.循环不变量的证明</h3><ul><li>初始化:证明循环不变量在循环开始前为真;</li><li>保持:证明每次循环之后循环不变式仍为真;</li><li>终止:循环可以有限次终止。</li></ul><h3 id="2-时间复杂度的分析"><a href="#2-时间复杂度的分析" class="headerlink" title="2.时间复杂度的分析"></a>2.时间复杂度的分析</h3><ul><li>整个算法的执行时间是执行所有语句的时间之和;</li><li>算法的执行时间可能依赖于给定的输入,即使规模相同</li><li>分析执行时间时可以分析算法的最坏执行情况、最好执行情况、平均执行情况。</li></ul><h3 id="3-算法的五个特性"><a href="#3-算法的五个特性" class="headerlink" title="3.算法的五个特性"></a>3.算法的五个特性</h3><ul><li><p>确定性</p></li><li><p>能行性</p></li><li><p>输入</p></li><li><p>输出</p></li><li><p>有穷性</p><blockquote><p>仅仅不满足有穷性规则的算法称为计算过程,如操作系统</p></blockquote></li></ul><h2 id="二、算法渐近"><a href="#二、算法渐近" class="headerlink" title="二、算法渐近"></a>二、算法渐近</h2><h3 id="1-限界函数"><a href="#1-限界函数" class="headerlink" title="1.限界函数"></a>1.限界函数</h3><h4 id="1-上界函数"><a href="#1-上界函数" class="headerlink" title="(1)上界函数"></a>(1)上界函数</h4><p>上界函数描述了<strong>算法最坏情况下的时间复杂度</strong>,记为$f(n)\inΟ(g(n))$或$f(n)=Ο(g(n))$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp2-%E4%B8%8A%E7%95%8C%E5%87%BD%E6%95%B0.png!blogimg" class="" width="400" title="上界函数"><h4 id="2-下界函数"><a href="#2-下界函数" class="headerlink" title="(2)下界函数"></a>(2)下界函数</h4><p>下界函数描述了<strong>渐进下界</strong>,记为$f(n)\in\Omega(g(n))$或$f(n)=\Omega(g(n))$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp2-%E4%B8%8B%E7%95%8C%E5%87%BD%E6%95%B0.png!blogimg" class="" width="400" title="下界函数"><h4 id="3-渐近紧确界函数"><a href="#3-渐近紧确界函数" class="headerlink" title="(3)渐近紧确界函数"></a>(3)渐近紧确界函数</h4><p>渐近紧确界函数代表<strong>算法在最好和最坏情况下的计算时间就一个常数因子范围内而相同</strong>,既有$f(n) = \Omega(g(n))$,又有$f(n) = Ο(g(n))$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp2-%E6%B8%90%E8%BF%91%E7%B4%A7%E7%A1%AE%E7%95%8C.png!blogimg" class="" width="400" title="渐近紧确界"><h4 id="4-记号说明"><a href="#4-记号说明" class="headerlink" title="(4)记号说明"></a>(4)记号说明</h4><ul><li>这里的”$=$”不是通常相等的含义,<strong>代表属于</strong></li><li>$\Theta(1)$表示具有<strong>常量计算时间</strong>的复杂度,即算法的执行时间为一个固定量,与问题的规模$n$无关</li></ul><h4 id="5-非渐近紧确的上下界"><a href="#5-非渐近紧确的上下界" class="headerlink" title="(5)非渐近紧确的上下界"></a>(5)非渐近紧确的上下界</h4><ul><li>$o$<strong>记号</strong></li></ul><p>对任意正常数$c$,存在常数$n_0\gt 0$,使对所有的$n\geq n_0$,有$\vert f(n)\vert\leq c\vert g(n)\vert$,则记作:$f(n)=o(g(n))$</p><ul><li>$\omega$<strong>记号</strong></li></ul><p>对任意正常数$c$,存在常数$n_0\gt0$,使对所有的$n\geq n_0$,有$\vert f(n)\vert\geq c\vert g(n)\vert$,则记作:$f(n)=\omega(g(n))$</p><h3 id="2-估算复杂性定理"><a href="#2-估算复杂性定理" class="headerlink" title="2.估算复杂性定理"></a>2.估算复杂性定理</h3><ul><li>多项式定理:关于$n$的$m$次多项式与最高阶$n^m$同阶</li><li>$n^x(\log n)^y<n^{x+\varepsilon}$</li><li>$(\log n)^x<n$</li><li>$n^x<2^n$</li></ul><h3 id="3-上界函数定理"><a href="#3-上界函数定理" class="headerlink" title="3.上界函数定理"></a>3.上界函数定理</h3><ul><li>正线性性:$d(n)=O(f(n))$,则$ad(n)=O(f(n))$,其中$a>0$</li><li>加法律:$d(n)=O(f(n))$,$e(n)=O(g(n))$,则$d(n)+e(n)=O(f(n)+g(n))$</li><li>乘法律:$d(n)=O(f(n))$,$e(n)=O(g(n))$,则$d(n)e(n)=O(f(n)g(n))$</li><li>指数性质:$n^x=O(a^n)$,其中$x>0$,$a>1$</li><li>对数性质1:$\log n^x=O(\log n)$,其中$x>0$</li><li>对数性质2:$(\log n)^x=O(n^y)$,其中$x>0$,$y>0$</li></ul><h2 id="三、分治思想"><a href="#三、分治思想" class="headerlink" title="三、分治思想"></a>三、分治思想</h2><h3 id="1-分治原理"><a href="#1-分治原理" class="headerlink" title="1.分治原理"></a>1.分治原理</h3><p><strong>分治原理的基本思想:当问题规模比较大而无法直接求解时,将原始问题分解为几个规模较小、但类似于原始问题的子问题,然后递归地求解这些子问题,最后合并子问题的解以得到原始问题的解。</strong></p><ul><li><strong>基本策略:分解原问题,解决子问题,合并问题解</strong>。</li><li><strong>问题形式:跨越子数组的问题类型、合并子问题解的问题类型。</strong></li><li>计算复杂度:<a href="#2.%E9%80%92%E5%BD%92%E5%BC%8F%E6%B1%82%E8%A7%A3">递归式求解</a></li><li>实例:<a href="#3.%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F">归并排序</a>——$T(n)=2T(n/2)+cn$</li><li>实例:<a href="#4.%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E7%BB%84%E9%97%AE%E9%A2%98">最大子数组问题</a>——$T(n)=2T(n/2)+\Theta(n)$</li><li>实例:<a href="#6.%E6%9C%80%E8%BF%91%E7%82%B9%E5%AF%B9%E9%97%AE%E9%A2%98">最近点对问题</a>——$T(n)=2T(n/2)+O(n)$</li><li>实例:<a href="#7.%E9%80%86%E5%BA%8F%E5%AF%B9%E8%AE%A1%E6%95%B0%E9%97%AE%E9%A2%98">逆序对计数问题</a>——$T(n)=2T(n/2)+O(n)$</li><li>实例:<a href="#8.%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F">随机化快速排序的期望</a>——$T(n)=O(n\log n)$</li></ul><h3 id="2-递归式求解"><a href="#2-递归式求解" class="headerlink" title="2.递归式求解"></a>2.递归式求解</h3><h4 id="1-基本形式"><a href="#1-基本形式" class="headerlink" title="(1)基本形式"></a>(1)基本形式</h4><ul><li><p>求解递归式的目的是<strong>将递归式转换为渐近限界函数表示</strong>;</p></li><li><p>一般关系为$T(n) =T(n_1)+T(n_2)+f(n)$,其中$f(n)$表示<strong>除递归以外的代价</strong>。</p></li></ul><h4 id="2-预处理"><a href="#2-预处理" class="headerlink" title="(2)预处理"></a>(2)预处理</h4><ul><li><p><strong>减去一个低阶项</strong>以便于代换法中的归纳证明,如$cn-d$</p><blockquote><p>减去低阶项往往能够使数学证明顺利进行:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-%E5%87%8F%E5%8E%BB%E4%BD%8E%E9%98%B6%E9%A1%B9.png!blogimg" class="" width="400" title="减去低阶项"></blockquote></li><li><p>对<strong>取整符号</strong>进行简化</p><blockquote><p>如$T(n)=T(\lfloor n/2\rfloor)+T(\lceil n/2\rceil)+f(n)$,往往忽略上下取整函数,写作以下简单形式:$T(n)=2T(n/2)+f(n)$</p></blockquote></li><li><p>对<strong>对数或指数</strong>做代数转换</p><blockquote><p>改变变量来简化递归式:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-%E5%AF%B9%E6%8C%87%E6%95%B0%E5%8F%98%E9%87%8F%E4%BB%A3%E6%8D%A2.png!blogimg" class="" width="400" title="对指数变量代换"></blockquote></li><li><p>对<strong>限界函数项</strong>进行展开,便于化简</p><blockquote><p>对于$T(n)=3T(\lfloor n/4\rfloor)+\Theta(n^2)$,简化为$T(n)=3T(n/4)+cn^2$。</p></blockquote></li></ul><h4 id="3-求解方法"><a href="#3-求解方法" class="headerlink" title="(3)求解方法"></a>(3)求解方法</h4><h5 id="①代入法"><a href="#①代入法" class="headerlink" title="①代入法"></a>①代入法</h5><ul><li><p>利用熟悉或类似的递归式<strong>猜测解的形式</strong></p></li><li><p>用<strong>数学归纳法</strong>证明猜测的正确性,得出合适的$c$值以满足条件</p></li><li><p>讨论<strong>边界条件的正确性</strong></p><blockquote><p>代入法实例如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-%E4%BB%A3%E5%85%A5%E6%B3%95%E5%AE%9E%E4%BE%8B.png!blogimg" class="" width="400" title="代入法实例"></blockquote></li></ul><h5 id="②递归树"><a href="#②递归树" class="headerlink" title="②递归树"></a>②递归树</h5><ul><li><p>在内部节点中表达除递归以外的代价</p><blockquote><p>对于$T(n)=aT(n/b)+f(n)$,一般假设$n=b^k$,$k=\log_bn$简化计算</p></blockquote></li><li><p>列出递归树直至叶子节点,得到递归树高度</p><blockquote><p>递归至叶子节点后,递归树的层数一般为$\log_bn+1$</p><p>举例如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-%E9%80%92%E5%BD%92%E6%A0%91-1.png!blogimg" class="" width="400" title="递归树-1"></blockquote></li><li><p>计算内部某层节点的总代价、叶子节点总代价、树的总代价</p><blockquote><p>通过计算前几层节点的总代价,得到内部某层节点的总代价的<strong>通式</strong>;</p><p>计算叶子节点的数目,假设为$num$,则叶子节点的总代价为$\Theta(num)$;</p><p>根据等比数列求和公式得到总代价。</p><p>计算如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-%E9%80%92%E5%BD%92%E6%A0%91-2.png!blogimg" class="" width="400" title="递归树-2"></blockquote></li><li><p>根据树的总代价猜测渐近限界函数</p><blockquote><p>猜测如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-%E9%80%92%E5%BD%92%E6%A0%91-3.png!blogimg" class="" width="400" title="递归树-3"></blockquote></li><li><p><strong>利用代换法证明</strong>猜测</p><blockquote><p>证明如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-%E9%80%92%E5%BD%92%E6%A0%91-4.png!blogimg" class="" width="400" title="递归树-4"></blockquote></li></ul><h5 id="③主方法"><a href="#③主方法" class="headerlink" title="③主方法"></a>③主方法</h5><p>设$a≥1$,$b>1$,设$f(n)$为渐近正的函数,$T(n)$是定义在非负整数上的递归式:$T(n)=aT(n/b)+f(n)$,其中$n/b$指$\lfloor n/b \rfloor$或$\lceil n/b \rceil$,则可使用以下定理求解递归式:</p><ul><li><p>若对于某常数$\varepsilon>0$,有$f(n)=O(n^{\log_ba-\varepsilon})$,则$T(n)=\Theta(n^{\log_ba})$</p><blockquote><p>该情况中$n^{\log_ba}$比较大,$f(n)$需<strong>多项式地小于</strong>$n^{\log_ba}$,即对某个常量$\varepsilon>0$,$f(n)$必须渐近地小于$n^{\log_ba}$,两者相差了一个$n^\varepsilon$因子,如$T(n)=2T(n/2)+n\log n$和$T(n)=4T(n/2)+n^2\log n$不满足条件</p></blockquote></li><li><p>若$f(n)=\Theta(n^{\log_ba})$,则$T(n)=\Theta(n^{\log_ba}\log n)$</p><blockquote><p>该情况中两个函数一样大,乘以对数因子$\log n$</p></blockquote></li><li><p>若对于某常数$\varepsilon>0$,有$f(n)=\Omega(n^{\log_ba+\varepsilon})$,且对常数$c<1$与足够大的$n$,有$af(n/b)\leq cf(n)$,则$T(n)=\Theta(f(n))$</p><blockquote><p>该情况中$f(n)$比较大,$f(n)$需<strong>多项式地大于</strong>$n^{\log_ba}$,并需要满足一个规则性条件$af(n/b)\leq cf(n)$,注意其中$c< 1$</p></blockquote></li></ul><h3 id="3-归并排序"><a href="#3-归并排序" class="headerlink" title="3.归并排序"></a>3.归并排序</h3><h4 id="1-问题描述"><a href="#1-问题描述" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><p>已知包含$n$个数字的序列$A[1,\dots,n]$,对其进行升序排序。</p><h4 id="2-问题分析"><a href="#2-问题分析" class="headerlink" title="(2)问题分析"></a>(2)问题分析</h4><ul><li>将数组$A$排序问题分解为$A[1,\dots,\lfloor\frac{n}{2}\rfloor]$和$A[\lfloor\frac{n}{2}\rfloor+1,\dots,n]$排序问题;</li><li>递归解决子问题得到两个有序的子数组;</li><li>然后再将两个子数组合并,合并的代价即为<strong>除递归以外的代价</strong>;</li><li>当数组被分解为长度为1时天然有序,从而产生局部有序性,进而进行两两合并操作。</li></ul><h4 id="3-分治策略"><a href="#3-分治策略" class="headerlink" title="(3)分治策略"></a>(3)分治策略</h4><ul><li><p>算法伪代码:</p><blockquote><p>$MERGE-SORT(A,left,right)$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-MERGE-SORT.png!blogimg" class="" width="400" title="MERGE-SORT"><p>$MERGE(A,left,mid,right)$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-MERGE.png!blogimg" class="" width="400" title="MERGE"></blockquote></li><li><p>时间复杂度</p><ul><li>递归式为$T(n)=2T(n/2)+O(n)$,其中$O(n)$为$MERGE$操作的时间代价;</li><li>时间复杂度为$O(n\log n)$。</li></ul></li></ul><h3 id="4-最大子数组问题"><a href="#4-最大子数组问题" class="headerlink" title="4.最大子数组问题"></a>4.最大子数组问题</h3><h4 id="1-问题描述-1"><a href="#1-问题描述-1" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li>寻找和最大的非空连续子数组</li><li>给定一个数组$X[1..n]$,对于任意一对数组下标为$l,r(l\leq r)$的非空子数组,其和记为$S(l,r)=\sum\limits_{i=l}^{r}{X[i]}$,求$S(l,r)$的最大值,记为$S_{max}$。</li></ul><h4 id="2-暴力求解"><a href="#2-暴力求解" class="headerlink" title="(2)暴力求解"></a>(2)暴力求解</h4><ul><li>枚举$n+C_n^2$种下标$l,r$组合,求出最大子数组之和;</li><li>处理每对下标组合最少的时间代价为常量;</li><li>时间复杂度为$\Omega(n^2)$。</li></ul><h4 id="3-分治策略-1"><a href="#3-分治策略-1" class="headerlink" title="(3)分治策略"></a>(3)分治策略</h4><ul><li><p>将子数组$A[low…high]$划分为两个规模尽量相等的子子数组;</p></li><li><p>分别求解$A[low…mid]$和$A[mid+1…high]$的最大子数组;</p></li><li><p>基于上述划分,存在三种连续子数组情况:$mid$左侧、跨越$mid$、$mid$右侧;</p></li><li><p>对于跨越$mid$的情况,从$mid$出发,分别向左和向右找出最大子区间并合并,这个步骤的代价即为<strong>除递归以外的代价</strong>,其时间复杂度为$\Theta(n^2)$;</p><blockquote><p> 算法$FIND-MAX-CROSSING-SUBARRAY$如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-FIND-MAX-CROSSING-SUBARRAY.png!blogimg" class="" width="400" title="FIND-MAX-CROSSING-SUBARRAY"></blockquote></li><li><p>对于其他两种情况,递归调用<strong>FIND-MAXIMUM-SUBARRAY</strong>即可;</p></li><li><p>求最大子数组问题的分治算法</p><blockquote><p><strong>FIND-MAXIMUM-SUBARRAY</strong>如下图:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-FIND-MAXIMUM-SUBARRAY.png!blogimg" class="" width="400" title="FIND-MAXIMUM-SUBARRAY"></blockquote></li><li><p>时间复杂度</p><ul><li>当$n=1$时,$T(n)=\Theta(1)$;</li><li>当$n>1$时,$T(n)=2T(n/2)+\Theta(n)$;</li><li>时间复杂度为$T(n)=\Theta(n\lg n)$。</li></ul></li></ul><h4 id="※-4-非递归的线性算法"><a href="#※-4-非递归的线性算法" class="headerlink" title="※(4)非递归的线性算法"></a>※(4)非递归的线性算法</h4><h3 id="※5-Strassen-矩阵乘法"><a href="#※5-Strassen-矩阵乘法" class="headerlink" title="※5.$Strassen$矩阵乘法"></a>※5.$Strassen$矩阵乘法</h3><h3 id="6-最近点对问题"><a href="#6-最近点对问题" class="headerlink" title="6.最近点对问题"></a>6.最近点对问题</h3><h4 id="1-问题描述-2"><a href="#1-问题描述-2" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li>已知平面上分布着点集$P$中的$n$个点$p_1,p_2,\dots,p_n$,点$i$的坐标记为$(x_i,y_i)$,$1\leq i\leq n$。</li><li>找出一对距离最近的点(允许两个点处于同一个位置)</li></ul><h4 id="2-暴力搜索"><a href="#2-暴力搜索" class="headerlink" title="(2)暴力搜索"></a>(2)暴力搜索</h4><ul><li>对每对点都计算距离,然后比较大小,找出其中的最小者</li><li>计算点之间的距离的时间复杂度为$O(n^2)$</li><li>比较得到最小距离的时间复杂度为$O(n^2)$</li></ul><h4 id="3-分治策略-2"><a href="#3-分治策略-2" class="headerlink" title="(3)分治策略"></a>(3)分治策略</h4><ul><li><p>排序:将所有点按照$x$坐标排序——$O(n\log n)$</p></li><li><p>划分:将点集分成左、右两半$P_L$和$P_R$</p><blockquote><p>定义$d_L$为$P_L$中最近点对距离,$d_R$为$P_R$中最近点对距离,$d_C$为跨越分割线的最近点对距离,这与最大子数组问题类似。</p></blockquote></li><li><p>改进:令$\delta=min(d_L,d_R)$,则有$d_C<\delta$,即$d_C$对应点对必然落在分割线两侧的$\delta$距离内,称之为$strip$,同时易得,$d_C$的两个点的$y$坐标相差也不会大于$\delta$,因此应该对点的$y$坐标也进行排序。</p></li><li><p>实现:假设搜索到$p_j$时,$p_j$与$p_i$的$y$坐标相差大于$\delta$,那么对于$p_i$而言更远的$p_j$就可以终止搜索,转而处理$p_i$后面的点$p_{i+1}$。</p><blockquote><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></pre></td><td class="code"><pre><span class="line">for i=1 to numPointsInStrip do</span><br><span class="line">for j=i+1 to numPointsInStrip do</span><br><span class="line">if y-coordinates of p[i] and p[j] differ by more than δ</span><br><span class="line">break;</span><br><span class="line">else if dist(p[i],p[j])<δ</span><br><span class="line"> δ=dist(p[i],p[j]);</span><br></pre></td></tr></table></figure></blockquote></li><li><p>时间复杂度</p><ul><li>在最坏的情况下,计算$d_C$的时间复杂度为$O(n)$,则最终递归式为$T(n)=2T(n/2)+O(n)$</li><li>※预排序</li><li>综上得到所有附加工作的总时间复杂度为$O(n)$,则$T(n)=2T(n/2)+cn=O(n\log n)$</li></ul></li></ul><h3 id="7-逆序对计数问题"><a href="#7-逆序对计数问题" class="headerlink" title="7.逆序对计数问题"></a>7.逆序对计数问题</h3><h4 id="1-问题描述-3"><a href="#1-问题描述-3" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li><p>在一个数组$A$中,称满足$i<j$且$A[i]>A[j]$的二元组$(A[i],A[j])$为逆序对</p><blockquote><p>在数组$A={4,6,8,3,5}$中,$(A[1],A[4])$即为一个逆序对</p></blockquote></li><li><p>现已知长度为$n$的数组$A[1..n]$,求其<strong>逆序对的总数</strong>$\sum\limits_{1\leq i\leq j\leq n}X_{i,j}$,其中当$A[i]>A[j]$时$X_{i,j}$为1,否则为0。</p></li></ul><h4 id="2-暴力枚举"><a href="#2-暴力枚举" class="headerlink" title="(2)暴力枚举"></a>(2)暴力枚举</h4><ul><li>对于每个元素$A[i]$,枚举$j(j>i)$,并统计逆序对数目;</li><li>时间复杂度为$O(n^2)$。</li></ul><h4 id="3-分治策略-3"><a href="#3-分治策略-3" class="headerlink" title="(3)分治策略"></a>(3)分治策略</h4><ul><li><p>将子数组$A[low\dots high]$划分为两个规模尽量相等的子子数组;</p></li><li><p>分别递归求解仅在$A[low\dots mid]$和$A[mid+1\dots high]$中的逆序对数目;</p></li><li><p>合并子问题的解时,求解<strong>跨越子数组</strong>的逆序对数目;</p></li><li><p>求解跨越子数组的逆序对数目</p><ul><li><p>直接求解:对于每个$A[j]\in A[mid+1\dots high]$,枚举$A[i]\in A[low\dots mid]$并统计逆序对数目——算法运行时间为$O(n^2)$,得到分治策略运行时间为$O(n^2)$;</p><blockquote><p>运行时间受制于跨越子数组的逆序对计数方法,数组的<strong>有序性</strong>通常有助于提高算法的运行时间。</p></blockquote></li><li><p>排序求解:分别对数组$A[low\dots mid]$和$A[mid+1\dots high]$进行排序,对于每个$A[j]\in A[mid+1\dots high]$,采用二分查找为其在$A[low\dots mid]$中定位,则$A[j]$在$A[low\dots mid]$定位点右侧的元素均可与$A[j]$构成逆序对——算法运行时间为$O(n\log n)$,得到分治策略运行时间为$O(n(\log n)^2)$;</p><blockquote><p>排序和二分查找均无再优化空间,但未将排序过程融入整个算法框架;</p><p><strong>排序未利用子数组有序性质</strong>——使用归并排序;</p><p>合并问题解的同时对数组进行排序,归并过程中可同时计算逆序对数目。</p></blockquote></li><li><p>归并求解:从左到右扫描$A[low\dots mid]$和$A[mid+1\dots high]$,如果$A[i]>A[j]$,统计逆序对,$j$向右移;否则$i$向右移——算法运行时间为$O(n)$,得到分治策略运行时间为$O(n\log n)$。</p></li></ul></li><li><p><strong>分而治之+归并求解</strong></p><blockquote><p>MergeCount:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-MERGECOUNT.png!blogimg" class="" width="400" title="MERGECOUNT"><p>CountInver:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-COUNTINVER.png!blogimg" class="" width="400" title="COUNTINVER"></blockquote></li><li><p>时间复杂度</p><ul><li>归并求解的算法运行时间为$o(n)$;</li><li>$T(n)=2T(n/2)+O(n)$;</li><li>时间复杂度为$T(n)=O(n\lg n)$。</li></ul></li></ul><h3 id="8-快速排序"><a href="#8-快速排序" class="headerlink" title="8.快速排序"></a>8.快速排序</h3><h4 id="1-问题描述-4"><a href="#1-问题描述-4" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li>选择排序和插入排序的时间复杂度均为$O(n^2)$;</li><li>归并排序简化分解,<strong>侧重合并</strong>,快速排序<strong>侧重分解</strong>,简化合并。</li></ul><h4 id="2-分治策略"><a href="#2-分治策略" class="headerlink" title="(2)分治策略"></a>(2)分治策略</h4><ul><li><p>选取固定位置主元$x$,如尾元素;</p></li><li><p>维护两个部分的右端点下标变量$x,y$;</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E7%A4%BA%E6%84%8F%E5%9B%BE.png!blogimg" class="" width="400" title="快速排序示意图"></li><li><p>考察数组元素$A[j]$,并<strong>只和主元比较</strong>:若$A[j]\leq x$,则交换$A[j]$和$A[i+1]$,$i$和$j$右移,否则$j$右移;</p></li><li><p>到达末尾后,把主元放在中间$(i+1)$处作为分界线;</p></li><li><p>以主元作为数组的划分,得到子数组分别进行PARTITION排序,排序后进行合并</p></li><li><p>伪代码如下:</p><blockquote><p>Partition:对每个子数组进行排序操作,返回主元位置$p$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-PARTITION.png!blogimg" class="" width="400" title="PARTITION"><p>QuickSort:利用Partition和分治策略进行快速排序</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-QUICKSORT.png!blogimg" class="" width="400" title="QUICKSORT"></blockquote></li><li><p>时间复杂度</p><ul><li><p>选取固定位置主元时最好情况下为$O(n\log n)$,最坏情况下为$O(n^2)$</p></li><li><p>选取随机位置主元,可以避免最坏情况的发生</p><blockquote><p>Randomized-Partition:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-RANDOMIZED-PARTITION.png!blogimg" class="" width="400" title="RANDOMIZED-PARTITION"><p>Randomized-QuickSort:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-RANDOMIZED-QUICKSORT.png!blogimg" class="" width="400" title="RANDOMIZED-QUICKSORT"></blockquote></li><li><p>随机化的快速排序的期望复杂度为$O(n\log n)$</p></li><li><p><strong>基于比较的排序,其时间复杂度的下界为</strong>$\Omega(n\log n)$。</p></li></ul></li></ul><h3 id="9-次序选择问题"><a href="#9-次序选择问题" class="headerlink" title="9.次序选择问题"></a>9.次序选择问题</h3><h4 id="1-基本概念"><a href="#1-基本概念" class="headerlink" title="(1)基本概念"></a>(1)基本概念</h4><ul><li><p>顺序统计量:在一个由$n$个元素组成的集合中,第$i$个顺序统计量$(order statistic)$是该集合中的第$i$小的元素</p></li><li><p>中位数(<strong>一般指下中位数</strong>)</p><ul><li>下中位数:$i=n/2$或$i=\lfloor(n+1)/2\rfloor$</li><li>上中位数:$i=n/2+1$或$i=\lceil(n+1)/2\rceil$</li></ul></li><li><p>选择问题:从$n$个元素的集合中选择第$i$个顺序统计量的问题形式化地归结为“选择问题”</p><ul><li>输入:一个包含$n$个(互异的)数的集合$A$和一个整数$i$,$1\leq i\leq n$</li><li>输出:元素$x\in A$,且$A$中恰好有$i-1$个其他元素小于它</li></ul></li><li><p>采用排序求解的方式解决选择问题时,其时间复杂度为$O(n\log n)$,可以求得所有元素的次序,选择元素的时间复杂度为$O(1)$。</p></li></ul><h4 id="2-期望为线性时间的选择算法"><a href="#2-期望为线性时间的选择算法" class="headerlink" title="(2)期望为线性时间的选择算法"></a>(2)期望为线性时间的选择算法</h4><ul><li><p>受启发于快速排序的Partition过程:</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-%E5%8F%97%E5%90%AF%E5%8F%91%E4%BA%8E%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F.png!blogimg" class="" width="400" title="受启发于快速排序"></blockquote></li><li><p>选择算法:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-1/Chp3-RANDOMIZED-SELECT.png!blogimg" class="" width="400" title="RANDOMIZED-SELECT"><blockquote><p>第1行检查$A[p..r]$中只包括一个元素的情况;</p><p>其余情况调用第3行的RANDOMIZED-PARTITION,将数组$A[p..r]$划分为两个子数组$A[p..q-1]$和$A[q+1..r]$<strong>(可能为空)</strong>,使前者的每个元素都小于$A[q]$,后者的每个元素都大于$A[q]$,称$A[q]$为主元;</p><p>第4行计算处于划分的低区的元素个数加1;</p><p>第5行检查$A[q]$是否为第$i$小的元素;</p><p>如果不是,则确定第$i$小的元素是在哪个子数组并在其中递归查找,当$i>k$时,要找的元素必定为$A[q+1..r]$中第$i-k$小的元素。</p></blockquote></li><li><p>最坏情况运行时间为$\Theta(n^2)$,期望运行时间为$\Theta(n)$</p></li></ul><h4 id="※-3-最坏情况为线性时间的选择算法"><a href="#※-3-最坏情况为线性时间的选择算法" class="headerlink" title="※(3)最坏情况为线性时间的选择算法"></a>※(3)最坏情况为线性时间的选择算法</h4>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>算法设计与分析笔记(二)</title>
<link href="/posts/2022/08/21/notes/Algorithm-Design-and-Analysis-2/"/>
<url>/posts/2022/08/21/notes/Algorithm-Design-and-Analysis-2/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Algorithm-Design-and-Analysis.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">算法设计与分析笔记</div> <div class="tag-link-sitename"> 点击下载算法设计与分析笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="四、动态规划"><a href="#四、动态规划" class="headerlink" title="四、动态规划"></a>四、动态规划</h2><h3 id="1-基本原理"><a href="#1-基本原理" class="headerlink" title="1.基本原理"></a>1.基本原理</h3><h4 id="1-基本概念"><a href="#1-基本概念" class="headerlink" title="(1)基本概念"></a>(1)基本概念</h4><ul><li>最优化问题:这一类问题的可行解可能有很多个。每个解都有一个值,我们希望寻找具有最优值的解(最小值或最大值);</li><li>最优解可能有多个;</li><li>根据描述约束条件和目标函数的数学模型的特性和问题的求解方法的不同,可分为:线性规划、整数规划、非线性规划、 动态规划等问题。</li></ul><h4 id="2-动态规划的步骤"><a href="#2-动态规划的步骤" class="headerlink" title="(2)动态规划的步骤"></a>(2)动态规划的步骤</h4><ol><li><p><strong>问题结构分析</strong>:刻画结构特征,<strong>给出问题的表示</strong>,并<strong>明确原始问题</strong>;</p></li><li><p><strong>递推关系建立</strong>:分析最优(子)结构特征,构造递推公式;</p><blockquote><p>问题的最优解由相关子问题最优解组合而成,子问题可以独立求解;</p><p>递推公式又称状态转移方程。</p></blockquote></li><li><p><strong>自底向上计算</strong>:确定计算顺序,计算最优解的值;</p><blockquote><p>子问题的无关性和重叠性</p><ul><li>两个子问题如果不共享资源,它们就是独立的,比如在分治算法中子问题相互独立;</li><li>重叠是指两个子问题实际上是同一个子问题,只是作为不同问题的子问题出现而已,如果暴力枚举,则会导致大量重叠子问题重复计算。</li></ul><p>重叠子问题的解决:动态规划<strong>付出额外空间保存结果</strong>,对每个子问题只求解一次。</p></blockquote></li><li><p><strong>最优方案追踪</strong>:利用辅助数组等记录决策过程,输出最优方案。</p></li></ol><h4 id="3-证明最优子结构性"><a href="#3-证明最优子结构性" class="headerlink" title="(3)证明最优子结构性"></a>(3)证明最优子结构性</h4><ul><li>证明问题满足最优性原理是实施动态规划的必要条件。</li><li>证明的通用模式<ol><li>证明问题最优解的第一个组成部分是做出一个选择,例如,选择钢条第一次切割位置,选择矩阵链的划分位置等。</li><li>利用<strong>“剪切一粘贴”</strong>技术证明<ul><li>作为原问题最优解的组成部分,每个子问题的解就是它本身的最优解。</li><li>利用反证法:假定子问题的解不是其自身的最优解,那么我们就可以从原问题的解中<strong>“剪切”</strong>掉这些非最优解,将最优解<strong>“粘贴”</strong>进去,从而得到原问题一个更优的解,<strong>这与最初的解是原问题最优解的前提假设矛盾</strong>。</li></ul></li></ol></li></ul><h4 id="4-备忘机制"><a href="#4-备忘机制" class="headerlink" title="(4)备忘机制"></a>(4)备忘机制</h4><p>为了避免对重叠子问题的重复计算,在递归过程中加入<strong>备忘</strong>机制。当第一次遇到子问题时,计算其解,并将结果存储在备忘表中;而其后遇到同一个子问题时,通过简单的查表即可返回其解,无需重复计算,节省了时间。</p><h4 id="5-重构最优解"><a href="#5-重构最优解" class="headerlink" title="(5)重构最优解"></a>(5)重构最优解</h4><p>通常定义一个表,<strong>记录每个子问题所做的决策</strong>。当求出最优解的值后,利用该表<strong>回溯</strong>即可得到最优方案。</p><h4 id="6-子问题图"><a href="#6-子问题图" class="headerlink" title="(6)子问题图"></a>(6)子问题图</h4><ul><li><strong>子问题图用于描述子问题与子问题之间的依赖关系</strong>。</li><li>子问题图是一个有向图,每个顶点唯一地对应一个子问题。</li><li>若求子问题$x$的最优解时直接用到子问题$y$的最优解,则在子问题图中就会有一条从子问题$x$的顶点到子问题$y$的顶点的有向边。</li><li>子问题图是<strong>自顶向下递归调用树的“简化版”</strong>。</li><li>在自底向上方法中,对于任何子问题,仅当它依赖的所有子问题都求解完成,才会求解它。</li><li>子问题的数目等于顶点数;</li><li>一个子问题的求解时间与子问题图中对应顶点的“出度”成正比;</li><li>一般情况下,<strong>动态规划算法的运行时间与顶点和边的数量至少呈线性关系</strong>。</li></ul><h3 id="2-01背包问题"><a href="#2-01背包问题" class="headerlink" title="2.01背包问题"></a>2.01背包问题</h3><h4 id="1-问题描述"><a href="#1-问题描述" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><p>$n$个商品组成集合$O$,每个商品有两个属性$v_i$和$p_i$,分别表示体积和价格,背包容量为$C$</p><p>试求解一个商品子集$S\subseteq O$,使得$max\sum\limits_{i\in S}{p_i}$且$\sum\limits_{i\in S}{v_i}\leq C$。</p><h4 id="2-问题分析"><a href="#2-问题分析" class="headerlink" title="(2)问题分析"></a>(2)问题分析</h4><ul><li>可以选取以下策略:<ul><li>策略1:按商品价格由高到低排序,优先挑选价格高的商品</li><li>策略2:按商品体积由小到大排序,优先挑选体积小的商品</li><li>策略3:按商品价值与体积的比由高到低排序,优先挑选比值高的商品</li></ul></li><li>以上三种策略<strong>都不能达到最优解</strong></li></ul><h4 id="3-暴力枚举"><a href="#3-暴力枚举" class="headerlink" title="(3)暴力枚举"></a>(3)暴力枚举</h4><ul><li><p>枚举所有组合共$2^n-1$种情况,并检查体积约束</p></li><li><p>伪代码如下:</p><blockquote><p>$KnapsackSR(i,c)$:前$i$个商品中,容量为$c$时为最优解</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-KnapsackSR.png!blogimg" class="" width="400" title="KnapsackSR"></blockquote></li><li><p>时间复杂度为$O(2^n)$</p></li></ul><h4 id="4-带备忘递归-自顶向下"><a href="#4-带备忘递归-自顶向下" class="headerlink" title="(4)带备忘递归(自顶向下)"></a>(4)带备忘递归(自顶向下)</h4><ul><li><p>记录子问题解,避免重复计算</p></li><li><p>伪代码如下:</p><blockquote><p>$KnapsackMR(i,c)$:带备忘的递归求解</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-KnapsackMR.png!blogimg" class="" width="400" title="KnapsackMR"><p>构造备忘录$P[i,c]$,表示在前$i$个商品中选择,背包容量为$c$时的最优解</p></blockquote></li></ul><h4 id="5-递推计算-自底向上"><a href="#5-递推计算-自底向上" class="headerlink" title="(5)递推计算(自底向上)"></a>(5)递推计算(自底向上)</h4><ul><li><p>递推公式:$P[i,c]=max{P[i-1,c-v[i]]+p[i],P[i-1,c]}$;</p></li><li><p>使用$Rec[i,c]$记录决策过程,选择时为1,否则为0;</p></li><li><p>回溯解决方案时,倒序判断是否选择商品,根据选择结果,确定最优子问题;</p></li><li><p>伪代码如下:</p><blockquote><p>$KnapsackDP(n,p,v,C)$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-KnapsackDP-1.png!blogimg" class="" width="400" title="KnapsackDP-1"><p>对数组进行初始化,默认每个商品都不选择;</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-KnapsackDP-2.png!blogimg" class="" width="400" title="KnapsackDP-2"><p>在$for$循环中依次计算子问题:<br>对于每个子问题,如果商品体积$v[i]\leq c$且选择该商品后得到的总价格$(P[i-1,c-v[i]]+p[i])$高,则选择该商品并更新$P[i,c]$,否则不选择该商品;</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-KnapsackDP-3.png!blogimg" class="" width="400" title="KnapsackDP-3"><p>倒序判断是否选择了该商品,如果选择了该商品,则回溯子问题。</p></blockquote></li><li><p>求解表格的算法复杂度为$O(n\cdot C)$。</p></li></ul><h3 id="※3-最大子数组问题"><a href="#※3-最大子数组问题" class="headerlink" title="※3.最大子数组问题"></a>※3.最大子数组问题</h3><p>使用分治算法解决最大子数组问题的时间复杂度为$O(n\log n)$,使用动态规划方法能达到时间复杂度仅为$O(n)$的算法。</p><h3 id="4-钢条切割问题"><a href="#4-钢条切割问题" class="headerlink" title="4.钢条切割问题"></a>4.钢条切割问题</h3><h4 id="1-问题描述-1"><a href="#1-问题描述-1" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li><p>给定一段长度为$n$英寸的钢条和一个价格表$P$,切割工序本身没有成本支出,求切割钢条方案,使得销售收益 $r_n$ 最大。</p></li><li><p>假定出售一段长度为i英寸的钢条的价格为$p_i(i=1,2,\dots)$,下面是价格表$P$:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-%E4%BB%B7%E6%A0%BC%E8%A1%A8P.png!blogimg" class="" width="400" title="价格表P"></li></ul><h4 id="2-问题分析-1"><a href="#2-问题分析-1" class="headerlink" title="(2)问题分析"></a>(2)问题分析</h4><ul><li>每一英寸都可切割,共有$n-1$个切割点,因此长度为$n$英寸的钢条共有$2^{n-1}$中不同的切割方案。</li><li>如果一个最优解将总长度为$n$的钢条切割为$k$段,每段的长度为$i_j(1\leq j\leq k)$,则有$n=i_1+i_2+\dots+i_k$,得到的最大收益为$r_n=p_{i_1}+p_{i_2}+\dots+p_{i_k}$</li><li>首次切割后,将两段钢条看成<strong>两个独立的钢条切割问题</strong>实例。若分别获得两段钢条的最优切割收益$r_j$和$r_{n-j}$,则原问题的解就可以通过<strong>组合这两个相关子问题的最优子解</strong>获得。</li><li>也即<strong>最优子结构性</strong>——如果$r_n=r_i+r_{n-i}$是最优切割收益,则$r_i$、$r_{n-i}$是相应子问题的最优切割收益。</li></ul><h4 id="3-朴素递归"><a href="#3-朴素递归" class="headerlink" title="(3)朴素递归"></a>(3)朴素递归</h4><ul><li><p>$r_n=\max\limits_{1\leq i\leq n}{(p_i+r_{n-i})}$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-CUT-ROD.png!blogimg" class="" width="400" title="CUT-ROD"></li><li><p>运行效率很差,存在一些相同的子问题重复调用解决</p></li><li><p>$T(n)=1+\sum\limits_{j=0}^{n-1}{T(j)}$,也即$T(n)=2^n$</p></li></ul><h4 id="4-带备忘递归-自顶向下-1"><a href="#4-带备忘递归-自顶向下-1" class="headerlink" title="(4)带备忘递归(自顶向下)"></a>(4)带备忘递归(自顶向下)</h4><ul><li><p>依旧按照<strong>递归</strong>的形式编写过程,但处理过程中会<strong>保存每个子问题的解</strong>。</p></li><li><p>具体实现如下:</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-MEMOIZED-CUT-ROD.png!blogimg" class="" width="400" title="MEMOIZED-CUT-ROD"></blockquote><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-MEMOIZED-CUT-ROD-AUX.png!blogimg" class="" width="400" title="MEMOIZED-CUT-ROD-AUX"></blockquote><blockquote><p>其中辅助数组$r[0\dots n]$用于保存子问题的结果。</p><p>初始化为$-\infty$;</p><p>当有新的结果时,$r[n]$保存结果$q$;</p><p>当$r[n]\geq 0$时,直接引用其中已保存的值。</p></blockquote></li><li><p>运行时间为$\Theta(n^2)$</p></li></ul><h4 id="5-自底向上"><a href="#5-自底向上" class="headerlink" title="(5)自底向上"></a>(5)自底向上</h4><ul><li><p>将子问题按规模排序,按<strong>由小到大的顺序顺次求解</strong>,当求解某个子问题时,它所依赖的<strong>更小子问题都已求解完毕</strong>,结果已经保存,故可以直接引用并组合出它自身的解</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-BOTTOM-UP-CUT-ROD.png!blogimg" class="" width="400" title="BOTTOM-UP-CUT-ROD"></li><li><p>运行时间为$\Theta(n^2)$,相比自顶向下的方法具有更小的系数</p></li></ul><h4 id="6-自底向上-给出切割方案"><a href="#6-自底向上-给出切割方案" class="headerlink" title="(6)自底向上(给出切割方案)"></a>(6)自底向上(给出切割方案)</h4><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-EXTENDED-BOTTOM-UP-CUT-ROD.png!blogimg" class="" width="400" title="EXTENDED-BOTTOM-UP-CUT-ROD"><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-PRINT-CUT-ROD-SOLUTION.png!blogimg" class="" width="400" title="PRINT-CUT-ROD-SOLUTION"><h3 id="5-矩阵链乘法问题"><a href="#5-矩阵链乘法问题" class="headerlink" title="5.矩阵链乘法问题"></a>5.矩阵链乘法问题</h3><h4 id="1-基本背景"><a href="#1-基本背景" class="headerlink" title="(1)基本背景"></a>(1)基本背景</h4><ul><li>已知$A$为$p\times r$的矩阵,$B$为$r\times q$的矩阵,则$A$与$B$的乘积是一个$p\times q$的矩阵,矩阵相乘需要进行$pqr$次标量乘法运算。</li><li>$n$个要连续相乘的矩阵构成一个矩阵链$\langle A_1,A_2,\dots,A_n\rangle$,要计算这$n$个矩阵的连乘乘积:$A_1A_2\dots A_n$,称为矩阵链乘问题。<ul><li>矩阵链乘满足结合律,不满足交换律。</li><li>不同的加括号方式代表不同的<strong>计算模式</strong>,而不同的计算模式计算矩阵链乘积的<strong>代价不同</strong>。</li></ul></li></ul><h4 id="2-问题描述"><a href="#2-问题描述" class="headerlink" title="(2)问题描述"></a>(2)问题描述</h4><ul><li>给定$n$个矩阵的链,记为$\langle A_1,A_2,\dots,A_n\rangle$,其中$i=1,\dots,n$,矩阵$A_i$的维数为$p_{i-1}\times P_i$。</li><li>求<strong>“完全括号化方案”</strong>,使得计算乘积$A_1A_2\dots A_n$所需的标量乘法次数最小。</li><li>穷举所有方案的数量:当$n=1$时,$P(n)=1$,当$n\geq 2$时,$P(n)=\sum\limits_{k=1}^{n-1}{P(k)P(n-k)}$,证明得到时间复杂度为$P(n)=\Omega(2^n)$</li></ul><h4 id="3-动态规划"><a href="#3-动态规划" class="headerlink" title="(3)动态规划"></a>(3)动态规划</h4><ul><li><p>最优括号化方案的结构特征——寻找最优子结构</p><ul><li>整体的最优括号化方案可以通过寻找使最终标量乘法次数最小的两个最优括号化子方案得到,形如:$(A_1A_{i+1}\dots A_k)(A_{k+1}\dots A_n)$</li></ul></li><li><p>递推求解方案</p><ul><li>递推求解公式</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-%E7%9F%A9%E9%98%B5%E9%93%BE%E4%B9%98%E9%80%92%E5%BD%92%E5%BC%8F.png!blogimg" class="" width="400" title="矩阵链乘递归式"><ul><li>使用$s[i,j]$保存$A_iA_{i+1}\dots A_j$最优括号化方案的分割点位置$k$</li></ul></li><li><p>计算最优代价</p><ul><li><p>对应子问题为$\Theta(n^2)$个,存在<strong>子问题重叠</strong>现象,同最优子结构性一样,这也是应用动态规划的标识。</p></li><li><p>采用自底向上法替代该递推求解公式</p><ul><li><p>算法的输入为序列$p=\langle p_0,p_1,\dots,p_n\rangle$,长度为$p.length=n+1$</p></li><li><p>算法伪代码如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-MATRIX-CHAIN-ORDER.png!blogimg" class="" width="400" title="MATRIX-CHAIN-ORDER"><blockquote><p>第$3\thicksim 4$行计算$m[i,i]=0$</p><p>第$5\thicksim 13$行计算不同矩阵链长度下$m[i,i+l-1]$的最小计算代价,长度依次递增计算。</p><p>可以使用一个上三角矩阵表表示$m[i,j]$和$s[i,j]$</p><p>具体实例如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-MATRIX-CHAIN-ORDER-SAMPLE.png!blogimg" class="" width="400" title="MATRIX-CHAIN-ORDER-SAMPLE"></blockquote></li><li><p>算法运行时间为$\Omega(n^3)$,空间复杂度为$\Theta(n^2)$</p></li></ul></li></ul></li><li><p>构造最优解</p><ul><li><p>$s[i,j]$记录了$A_iA_{i+1}\dots A_j$的最优括号化方案的“首个”分割点$k$。基于$s[i,j]$,对$A_iA_{i+1}\dots A_j$的括号化方案是:</p><p> $(A_iA_{i+1}\dots A_{s[i,j]})(A_{s[i,j]+1}\dots A_j)$</p></li><li><p>打印结果的伪代码如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-PRINT-OPTIMAL-PARENS.png!blogimg" class="" width="400" title="PRINT-OPTIMAL-PARENS"></li></ul></li></ul><h3 id="6-最长公共子序列"><a href="#6-最长公共子序列" class="headerlink" title="6.最长公共子序列"></a>6.最长公共子序列</h3><h4 id="1-基本背景-1"><a href="#1-基本背景-1" class="headerlink" title="(1)基本背景"></a>(1)基本背景</h4><ul><li><p>子序列</p><p>给定两个序列$X=\langle X_1,X_2,\dots,X_n\rangle$和序列$Z=\langle z_1,z_2,\dots,z_k\rangle$,若存在$X$的一个严格递增下标序列$\langle i_1,i_2,\dots,i_k\rangle$,使得对所有$j=1,2,\dots,k$,有$x_{i_j}=z_j$,则称$Z$是$X$的子序列。</p><blockquote><p>$Z=\langle B,C,D,B\rangle$是$X=\langle A,B,C,B,D,A,B\rangle$的一个子序列,对应下标序列为$\langle 2,3,5,7\rangle$。</p></blockquote></li><li><p>公共子序列</p><p>对给定的两个序列$X$和$Y$,若序列$Z$既是$X$的的子序列,也是$Y$的子序列,则称$Z$是$X$和$Y$的公共子序列。</p><blockquote><p>$X=\langle A,B,C,B,D,A,B\rangle$,$Y=\langle B,D,C,A,B,A\rangle$,则序列$\langle B,C,A\rangle$是$X$和$Y$的一个公共子序列。</p></blockquote></li><li><p>最长公共子序列(LCS)</p><p>两个序列的长度最大的公共子序列称为它们的最长公共子序列。</p><blockquote><p>$\langle B,C,A\rangle$是上面$X$和$Y$的一个公共子序列,但不是$X$和$Y$的最长公共子<br>序列。最长公共子序列是$\langle B,C,B,A\rangle$。</p></blockquote></li><li><p>前缀</p><p>给定一个序列$X=\langle x_1,x_2,\dots,x_m\rangle$,对于$i=0,1,\dots,m$,定义$X$的第$i$个前缀为$X_i=\langle x_1,x_2,\dots,x_i\rangle$,即前$i$个元素构成的子序列。</p><blockquote><p>$X=\langle A,B,C,B,D,A,B\rangle$,则$X_4=\langle A,B,C,B\rangle$,$X_0=\Phi$。</p></blockquote></li></ul><h4 id="2-最优子结构性"><a href="#2-最优子结构性" class="headerlink" title="(2)最优子结构性"></a>(2)最优子结构性</h4><p>两个序列的一个$LCS$也包含了两个序列的前缀的$LCS$,即$LCS$问题具有最优子结构性质。</p><blockquote><p>定理:设有序列$X=\langle x_1,x_2,\dots,x_m\rangle$和$Y=\langle y_1,y_2,\dots,y_n\rangle$,并设序列$Z=\langle z_1,z_2,\dots,z_k\rangle$为$X$和$Y$的任意一个$LCS$。</p><p>(1)若$x_m=y_n$,则$z_k=x_m=y_n$,且$Z_{k-1}$是$X_{m-1}$和$Y_{n-1}$的一个$LCS$。</p><p>(2)若$x_m\ne y_n$,则$z_k\ne x_m$蕴含$Z$是$X_{m-1}$和Y的一个$LCS$。</p><p>(3)若$x_m\ne y_n$,则$z_k\ne y_n$蕴含$Z$是$X$和$Y_{n-1}$的一个$LCS$。</p></blockquote><h4 id="3-递推关系式"><a href="#3-递推关系式" class="headerlink" title="(3)递推关系式"></a>(3)递推关系式</h4><p>记$c[i,j]$为前缀序列$X_i$和$Y_j$的一个$LCS$的长度,则有</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-LCS%E9%80%92%E6%8E%A8%E5%85%B3%E7%B3%BB%E5%BC%8F.png!blogimg" class="" width="400" title="LCS递推关系式"><blockquote><p>1)若$i=0$或$j=0$,即其中一个序列的长度为零,则$LCS$的长度为0,$LCS=\Phi$;</p><p>2)若$x_i=y_j$,则$X_i$和$Y_j$的$LCS$是在$X_{i-1}$和$Y_{j-1}$的$LCS$之后附加将$x_i$得到的,所以</p><p>$c[i,j]=c[i-1,j-1]+1$;</p><p>3)若$x_i\ne y_j$,则$X_i$和$Y_j$的$LCS$的最后一个字符不会是$x_i$或$y_j$(不可能同时等于两者,或与两者都不同),此时该$LCS$应等于$X_{i-1}$和$Y_j$的$LCS$与$X_i$和$Y_{j-1}$的$LCS$之中的较长者。所以</p><p>$c[i,j]=max(c[i-1,j],c[i,j-1])$。</p></blockquote><h4 id="4-自底向上"><a href="#4-自底向上" class="headerlink" title="(4)自底向上"></a>(4)自底向上</h4><ul><li><p>过程$LCS-LENGTH(X,Y)$用来求序列$X=\langle x_1,x_2,\dots,x_m\rangle$和$Y=\langle y_1,y_2,\dots,y_n\rangle$的$LCS$的长度,其时间复杂度为$O(mn)$。</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-LCS-LENGTH.png!blogimg" class="" width="400" title="LCS-LENGTH"><blockquote><p>表$c[1..m,1..n]$中包含每一阶段的$LCS$长度,$c[m,n]$等于$X$和$Y$的$LCS$的长度。</p><p>表$b[1..m,1..n]$记录当前$c[i,j]$的计值情况,以此来构造该$LCS$。</p><p>下图给出了在$X=\langle A,B,C,B,D,A,B\rangle$和$Y=\langle B,D,C,A,B,A\rangle$上运行$LCS-LENGTH$计算出的表:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-LCS-LENGTH-SAMPLE.png!blogimg" class="" width="400" title="LCS-LENGTH-SAMPLE"><blockquote><p>1)第$i$行和第$j$列中的方块包含了$c[i,j]$的值以及$b[i,j]$记录的箭头。</p><p>2)对于$i,j>0$,项$c[i,j]$仅依赖于是否有$x_i=y_j$及项$c[i-1,j]$、$c[i,j-1]$、$c[i-1,j-1]$的值。</p><p>3)为了重构一个$LCS$,从右下角开始跟踪$b[i,j]$箭头即可</p><p>4)图中,$c[7,6]=4$,$LCS(X,Y)=\langle B,C,B,A\rangle$。</p></blockquote></blockquote></li></ul><h4 id="5-构建最优解"><a href="#5-构建最优解" class="headerlink" title="(5)构建最优解"></a>(5)构建最优解</h4><ul><li><p>借助$b[i,j]$反序输出$LCS$,由于每一次循环使$i$或$j$减1,最终$m=0$,$n=0$,算法结束,所以$PRINT-LCS$的时间复杂度为$O(m+n)$。</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-PRINT-LCS.png!blogimg" class="" width="400" title="PRINT-LCS"></li><li><p>改进:去掉表$b$,直接基于$c$求$LCS$</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></pre></td><td class="code"><pre><span class="line">PRINT-LCS-WITHOUTAUXI(c, X, Y, i, j)</span><br><span class="line"> if c[i, j] == 0</span><br><span class="line"> return</span><br><span class="line"> if X[i] == Y[j]</span><br><span class="line"> PRINT-LCS-WITHOUTAUXI(c, X, Y, i - 1, j - 1)</span><br><span class="line"> print X[i]</span><br><span class="line"> else if c[i - 1, j] >= c[i, j - 1]</span><br><span class="line"> PRINT-LCS-WITHOUTAUXI(c, X, Y, i - 1, j)</span><br><span class="line"> else</span><br><span class="line"> PRINT-LCS-WITHOUTAUXI(c, X, Y, i, j - 1)</span><br></pre></td></tr></table></figure></li><li><p>改进:算法中,每个$c[i,j]$的计算仅需$c$的两行的数据:正在被计算的一行和前面的一行。</p></li></ul><h3 id="※7-最长公共子串"><a href="#※7-最长公共子串" class="headerlink" title="※7.最长公共子串"></a>※7.最长公共子串</h3><h3 id="8-最优二叉搜索树"><a href="#8-最优二叉搜索树" class="headerlink" title="8.最优二叉搜索树"></a>8.最优二叉搜索树</h3><h4 id="1-基本背景-2"><a href="#1-基本背景-2" class="headerlink" title="(1)基本背景"></a>(1)基本背景</h4><ul><li><p>二叉搜索树$T$是一棵二元树,它或者为空,或者其每个结点含有一个可以比较大小的数据元素,且有:</p><ul><li>$T$的左子树的所有元素比根结点中的元素小;</li><li>$T$的右子树的所有元素比根结点中的元素大;</li><li>$T$的左子树和右子树也是二叉搜索树。</li></ul></li><li><p>给定一个$n$个关键字的升序序列$K=\langle k_1,k_2,\dots,k_n\rangle$,对每个关键字$k_i$,都有一个概率$p_i$表示其被搜索的频率。根据$k_i$和$p_i$构建一个二叉搜索树$T$,每个$k_i$对应树中的一个结点。</p></li><li><p>引入外部结点$d_0,d_1,\dots,d_n$,用来表示不在$K$中的值,称为伪关键字。</p><ul><li><p>伪关键字在$T$中对应外部结点,共有$n+1$个。</p><blockquote><p><strong>扩展二叉树</strong>:内结点表示关键字$k_i$,外结点(叶子结点)表示$d_i$。</p></blockquote></li><li><p>每个$d_i$代表一个区间,$d_0$表示所有小于$k_1$的值,$d_n$表示所有大于$k_n$的值,对于$i=1,\dots,n-1$,$d_i$表示所有在$k_i$和$k_{i+1}$之间的值。</p></li><li><p>每个$d_i$也有一个概率$q_i$,表示搜索对象$x$恰好落入区间$d_i$的频率。</p></li></ul></li><li><p>一次搜索的代价等于从根结点开始访问结点的数量(<strong>包括外部结点</strong>)</p><blockquote><p>从根结点开始访问结点的数量等于<u>结点在$T$中的深度+1</u>,记$depth_{T(i)}$为结点$i$在$T$中的深度</p></blockquote></li><li><p>二叉搜索树$T$的期望代价为</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-SEARCH-COST-EXPECTATION.png!blogimg" class="" width="400" title="SEARCH-COST-EXPECTATION"></li><li><p><strong>最优二叉搜索树</strong>:对于给定的关键字及其概率集合,期望搜索代价最小的二叉搜索树。</p></li></ul><h4 id="2-动态规划"><a href="#2-动态规划" class="headerlink" title="(2)动态规划"></a>(2)动态规划</h4><ul><li><p>最优二叉搜索树的最优子结构</p><blockquote><p>如果$T$是一棵相对于关键字$k_1,\dots,k_n$和伪关键字$d_0,\dots,d_n$的最优二叉搜索树,则$T$中一棵包含关键字$k_i,\dots,k_j$的子树$T’$必然是相对于关键字$k_i,\dots,k_j$(和伪关键字$d_{i-1},\dots,d_j$)的最优二叉搜索子树。</p></blockquote></li><li><p>构造最优二叉搜索树</p><ul><li><p>求解包含关键字$k_i,\dots,k_j$的最优二叉搜索树,其中$i\geq 1$,$j\leq n$且 $j\geq i-1$</p></li><li><p>定义$e[i,j]$:为包含关键字$k_i,\dots,k_j$的最优二叉搜索树的期望搜索代价。</p></li><li><p>当$j=i-1$时,由于子树只包含伪关键字$d_{i-1}$,期望搜索代价为$e[i,i-1]=q_{i-1}$</p></li><li><p>当$j\geq i$时,从$k_i,\dots,k_j$中选择出根结点$k_r$,以此构建两个最优左右二叉搜索子树。</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-%E6%9C%80%E4%BC%98%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E6%9C%9F%E6%9C%9B%E6%90%9C%E7%B4%A2%E4%BB%A3%E4%BB%B7%E9%80%92%E5%BD%92%E5%85%AC%E5%BC%8F.png!blogimg" class="" width="400" title="最优二叉搜索树期望搜索代价递归公式"></li></ul></li><li><p>计算期望搜索代价</p><ul><li><p>定义$root[i,j]$,保存计算$e[i, j]$时,使$e[i, j]$取得最小值的$r$,$k_r$即为关键字$k_i,\dots,k_j$的最优二叉搜索(子)树的树根。在求出$e[1,n]$后,利用$root$即可构造出最终的最优二叉搜索树。</p></li><li><p>$w[1..n+1,0..n]$用于保存子树的结点概率之和,每个$w[i,j]$的计算时间仅为$\Theta(1)$</p><blockquote><p>满足<img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-BST-w%5Bi%2Cj%5D.png!blogimg" class="" width="400" title="BST-w[i,j]"></p></blockquote></li><li><p>自底向上的迭代计算,时间复杂度为$\Theta(n^3)$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-2/Chp4-OPTIMAL-BST.png!blogimg" class="" width="400" title="OPTIMAL-BST"></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>算法设计与分析笔记(三)</title>
<link href="/posts/2022/08/21/notes/Algorithm-Design-and-Analysis-3/"/>
<url>/posts/2022/08/21/notes/Algorithm-Design-and-Analysis-3/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Algorithm-Design-and-Analysis.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">算法设计与分析笔记</div> <div class="tag-link-sitename"> 点击下载算法设计与分析笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="五、贪心算法"><a href="#五、贪心算法" class="headerlink" title="五、贪心算法"></a>五、贪心算法</h2><h3 id="1-基本原理"><a href="#1-基本原理" class="headerlink" title="1.基本原理"></a>1.基本原理</h3><h4 id="1-基本概念"><a href="#1-基本概念" class="headerlink" title="(1)基本概念"></a>(1)基本概念</h4><ul><li>分步骤实施,它在每一步仅作出当时看起来最佳的选择,即<strong>局部最优的选择</strong>,并寄希望这样的选择最终能导致<strong>全局最优解</strong>。</li><li>实例:最小生成树问题的$Prim$算法、$Kruskal$算法,单源最短路径$Dijkstra$算法,<strong>分数背包</strong>。</li><li>贪心算法<strong>不总能对所有问题能求解</strong>,只是对一些问题确实有效,可以求出最优解或近似最优解。</li></ul><h4 id="2-贪心算法的步骤"><a href="#2-贪心算法的步骤" class="headerlink" title="(2)贪心算法的步骤"></a>(2)贪心算法的步骤</h4><ul><li><p>提出贪心策略:观察问题特征,构造贪心选择;</p></li><li><p>证明策略正确:假设最优方案,通过替换证明。</p><blockquote><p>对应每个贪心算法,都有一个动态规划算法,但动态规划算法要繁琐的多。</p></blockquote></li></ul><h4 id="3-贪心选择性质"><a href="#3-贪心选择性质" class="headerlink" title="(3)贪心选择性质"></a>(3)贪心选择性质</h4><p>可以通过做出局部最优(贪心)选择来构造全局最优解的性质。</p><p>贪心选择性使得我们进行选择时, 只需做出当前看起来最优的选择,而不用考虑子问题的解。</p><h3 id="2-活动选择问题"><a href="#2-活动选择问题" class="headerlink" title="2.活动选择问题"></a>2.活动选择问题</h3><h4 id="1-问题描述"><a href="#1-问题描述" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li><p>假定有一个活动的集合$S$含有$n$个活动${a_1,a_2,\dots,a_n}$,每个活动$a_i$都有一个开始时间$s_i$和结束时间$f_i$,$0\leq s_i<f_i<\infty$。同时,这些活动都要使用同一资源(如演讲会场),而这个资源在任何时刻只能供一个活动使用。</p></li><li><p>活动的兼容性:如果选择了活动$a_i$,则它在半开时间区间 $[s_i, f_i)$内占用资源。若两个活动$a_i$和$a_j$满足$[s_i, f_i)$与区间$[s_j, f_j)$不重叠,则称它们是<strong>兼容</strong>的。</p></li><li><p><strong>活动选择问题</strong>:假设活动按照结束时间单调递增排序,对给定的包含$n$个活动的集合$S$,在已知每个活动开始时间和结束时间的条件下,从中选出最多可兼容活动的子集合,称为<strong>最大兼容活动集合</strong>。</p><blockquote><p>考虑下列活动集合$S$:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp5-%E6%B4%BB%E5%8A%A8%E9%80%89%E6%8B%A9%E9%97%AE%E9%A2%98SAMPLE.png!blogimg" class="" width="400" title="活动选择问题SAMPLE"></blockquote></li></ul><h4 id="2-问题分析"><a href="#2-问题分析" class="headerlink" title="(2)问题分析"></a>(2)问题分析</h4><ul><li><p>设$S_{ij}$表示在$a_i$结束之后开始且在$a_j$开始之前结束的活动集合,$A_{ij}$表示$S_{ij}$的一个最大兼容活动子集,设$A_{ij}$包括活动$a_k$,则得到两个子问题——寻找$S_{ik}$和$S_{kj}$的最大兼容活动集合。</p><blockquote><p>图解如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp5-%E6%B4%BB%E5%8A%A8%E9%80%89%E6%8B%A9%E9%97%AE%E9%A2%98%E7%9A%84%E6%9C%80%E4%BC%98%E5%AD%90%E7%BB%93%E6%9E%84%E6%80%A7.png!blogimg" class="" width="400" title="活动选择问题的最优子结构性"></blockquote></li><li><p>必有:$A_{ik}$是$S_{ik}$一个最大兼容活动子集,$A_{kj}$是$S_{kj}$一个最大兼容活动子集。</p><blockquote><p>$A_{ij}=A_{ik}∪{a_k}∪A_{kj}$</p></blockquote></li><li><p>令$c[i,j]$表示集合$S_{ij}$的最优解大小,可使用动态规划方法解决</p></li></ul><h4 id="3-贪心算法"><a href="#3-贪心算法" class="headerlink" title="(3)贪心算法"></a>(3)贪心算法</h4><ul><li><p>每次总选择具有最早结束时间的兼容活动加入到集合$A$中——使剩余的可安排时间段最大化,以便安排尽可能多的兼容活动。</p></li><li><p>当输入的活动已按结束时间的递增顺序排列,贪心算法只需$O(n)$的时间即可选择出来$n$个活动的最大兼容活动集合。</p><blockquote><p>考虑任意非空子问题$S_k$,令$a_m$是$S_k$中结束时间最早的活动,则$a_m$必在$S_k$的某个最大兼容活动子集中。</p></blockquote></li><li><p>自顶向下的递归方法</p><blockquote><p>首先做出一个选择,然后求解剩下的子问题。每次选择将问题转化成一个规模更小的问题。</p><p>伪代码如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp5-RECURSIVE-ACTIVITY-SELECTOR.png!blogimg" class="" width="400" title="RECURSIVE-ACTIVITY-SELECTOR"><blockquote><p>第$2\thicksim 3$行查找$S_k$中最早结束的活动,循环检查$a_{k+1},a_{k+2},\dots,a_n$,直至找到第一个与$a_k$兼容的活动$a_m$,也即满足$s_m\geq f_k$。</p><p>如果成功找到$m$(也即$m\leq n$),则返回${a_m}$与$RECURSIVE-ACTIVITY-SELECTOR(s,f,m,n)$返回的$S_m$的最大子集的并集。</p><p>如果未成功找到$m$,则说明未找到与$a_k$兼容的活动,则返回$\Phi$。</p></blockquote></blockquote></li><li><p>迭代实现的贪心算法</p><blockquote><p>伪代码如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp5-GREEDY-ACTIVITY-SELECTOR.png!blogimg" class="" width="400" title="GREEDY-ACTIVITY-SELECTOR"><blockquote><p>$k$对应最后一个加入$A$的活动,$f_k$是$A$中活动的最大结束时间,若$m$的开始时间大于$f_k$,则$m$就是下一个被选中的活动。</p><p>算法的运行时间是$O(n)$。</p></blockquote></blockquote></li></ul><h3 id="3-带权活动选择问题"><a href="#3-带权活动选择问题" class="headerlink" title="3.带权活动选择问题"></a>3.带权活动选择问题</h3><h4 id="1-问题描述-1"><a href="#1-问题描述-1" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li>在活动选择问题中,如果每个活动都具有权重$w$,现寻找活动子集$S’$,使得权重和最大</li></ul><h4 id="2-问题分析-1"><a href="#2-问题分析-1" class="headerlink" title="(2)问题分析"></a>(2)问题分析</h4><ul><li>存在重叠子问题,可以使用动态规划求解</li><li>设$p[i]$表示在$a_i$开始前最后结束的活动编号</li><li>设$Rec[i]$表示是否选择问题$i$</li><li>设$D[i]$表示集合${a_1,a_2,a_3,\dots,a_i}$中兼容活动最大权重和</li><li>将活动按照结束时间升序进行排序,则可得到$D[i]=max{D[p[i]]+w_i,D[i-1]}$;其中不选择$a_i$时,其最大权重和即为$D[i-1]$,选择$a[i]$时,其最大权重和应为在$a_i$开始前最后结束的活动编号对应的最大权重和加上$w_i$,即$D[p[i]]+w_i$。</li></ul><h4 id="3-动态规划"><a href="#3-动态规划" class="headerlink" title="(3)动态规划"></a>(3)动态规划</h4><ul><li><p>伪代码如下</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp5-%E5%B8%A6%E6%9D%83%E6%B4%BB%E5%8A%A8%E9%80%89%E6%8B%A9%E9%97%AE%E9%A2%98.png!blogimg" class="" width="400" title="带权活动选择问题"></blockquote></li><li><p>时间复杂度为$O(n\log n)$</p></li></ul><h3 id="4-Huffman-编码"><a href="#4-Huffman-编码" class="headerlink" title="4.$Huffman$编码"></a>4.$Huffman$编码</h3><h4 id="1-基本概念-1"><a href="#1-基本概念-1" class="headerlink" title="(1)基本概念"></a>(1)基本概念</h4><ul><li><p>码字:每个字符用唯一的二进制串表示,称为码字。</p></li><li><p>定长编码:每个字符的编码长度一样。</p><blockquote><p>对于$a\thicksim f$六个字符,应采用3位码字编码,则10万个字符需用30万个二进制位编码。</p></blockquote></li><li><p>变长编码:每个字符赋予不同长度的码字。</p><blockquote><p>赋予高频字符短码字,低频字符长码字,字符的码字互不为前缀,这样才能唯一解码,同时能够提高编码效率。如$a$用1位的串0表示,$b$用3位的串101表示,$f$用4位的串1100表示等。</p></blockquote></li><li><p>前缀码$(Prefix code)$:任何码字都不是其它码字的前缀。</p><blockquote><p>前缀码可以简化解码过程,由于没有码字是其它码字的前缀,所以编码文件的开始部分是没有歧义的,可以唯一地转换回原字符。</p></blockquote></li><li><p>编码树:一种为表示字符二进制编码而构造的二叉树。</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp5-%E8%B5%AB%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81%E6%A0%91-SAMPLE.png!blogimg" class="" width="400" title="赫夫曼编码树-SAMPLE"><p>叶子结点:对应给定的字符,每个字符对应一个叶子结点。</p><p>编码构造:字符的二进制码字由根结点到该字符叶子结点的简单路径</p><p>路径表示:0代表转向左孩子,1代表转向右孩子</p></blockquote></li><li><p>最优字符编码方案总对应一棵<strong>满二叉树</strong>, 即每个非叶子结点都有两个孩子结点。</p></li></ul><h4 id="2-最优字符编码方案"><a href="#2-最优字符编码方案" class="headerlink" title="(2)最优字符编码方案"></a>(2)最优字符编码方案</h4><ul><li><p>符号表示</p><ul><li><p>设$C$为字母表</p><blockquote><p>对字母表$C$中的任意字符$c$,令属性$c.freq$表示字符$c$在文件中出现的频率</p><p>最优前缀码对应的树中恰好有$\vert C\vert$个叶子结点,每个叶子结点对应字母表中的一个字符,且恰有$\vert C\vert-1$个内部结点。</p></blockquote></li><li><p>设$T$表示一棵前缀编码树</p></li><li><p>设$d_T(c)$表示c的叶子结点在树T中的深度(根到叶子结点的路径长度)</p><blockquote><p>$d_T(c)$也表示字符$c$的码字长度</p></blockquote></li><li><p>设$B(T)$表示采用$T$编码时的文件编码长度,即$B(T)=\sum\limits_{c\in C}c.freq\cdot d_T(c)$,称$B(T)$为T的代价。</p><blockquote><p>使得$B(T)$最小的编码称为最优编码。</p><p>对给定的字符集和文件,$Huffman$编码是一种最优编码。</p></blockquote></li></ul></li></ul><h4 id="3-Huffman-编码"><a href="#3-Huffman-编码" class="headerlink" title="(3)$Huffman$编码"></a>(3)$Huffman$编码</h4><ul><li><p>算法$HUFFMAN$从$\vert C\vert$个叶子结点开始,每次选择频率最低的两个结点合并,将得到的新结点加入集合继续合并,这样执行$\vert C\vert-1$次<strong>合并</strong>后即可构造出一棵编码树——$Huffman$树。</p><blockquote><p>伪代码如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp5-HUFFMAN.png!blogimg" class="" width="400" title="HUFFMAN"><p>第2行用$C$中字符初始化最小优先队列$Q$;</p><p>第$3\thicksim 8$行的循环反复从队列中合并频率最低的结点$x$和$y$,合并为新结点$z$并替代之;</p><p>经过$n-1$次合并后,最后返回剩下的唯一结点——编码树的根结点</p><p>示例如下:</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp5-HUFFMAN-SAMPLE.png!blogimg" class="" width="400" title="HUFFMAN-SAMPLE"></blockquote></blockquote></li><li><p>时间复杂度的分析</p><ul><li><p>假设$Q$使用最小二叉堆实现,则其初始化花费$O(n)$的时间</p></li><li><p>循环的总代价是$O(n\lg n)$</p><blockquote><p>$for$循环共执行了$n-1$次,每次从堆中找出当前频率最小的两个结 点及把合并得到的新结点插入到堆中均花费$O(\lg n)$,所以循环的总代价是$O(n\lg n)$。</p></blockquote></li><li><p>因此,$HUFFMAN$的总运行时间$O(n\lg n)$</p></li></ul></li><li><p>※$Huffman$算法的正确性</p><ul><li>证明贪心选择性</li><li>证明最优子结构性</li></ul></li></ul><h3 id="5-分数背包问题"><a href="#5-分数背包问题" class="headerlink" title="5.分数背包问题"></a>5.分数背包问题</h3><h4 id="1-问题描述-2"><a href="#1-问题描述-2" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li>已知$n$个物品组成的集合O,每个物品有两个属性$v_i$和$p_i$,分别表示体积和价格;</li><li>背包容量为$C$;</li><li>试求解$S={x_i|1\leq i\leq n,0\leq x_i\leq 1}$,使得$max\sum\limits_{x_i\in S}x_ip_i$且$\sum\limits_{x_i\in S}x_iv_i\leq C$。</li></ul><h4 id="2-问题分析-2"><a href="#2-问题分析-2" class="headerlink" title="(2)问题分析"></a>(2)问题分析</h4><ul><li><p>采用贪心算法,每次选择<strong>最高性价比</strong>($p_i/v_i$)的物品,证明可得贪心解不劣于最优解</p></li><li><p>伪代码如下</p><blockquote><p>$FractionalKnapsack(n,p,v,C)$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp5-FractionlKnapsack.png!blogimg" class="" width="400" title="FractionlKnapsack"><p>当背包未装满且商品未装完时填入商品,商品体积不大于容量则全部装入,否则装入部分商品填满背包</p></blockquote></li><li><p>算法复杂度为$O(n\log n)$</p></li></ul><h2 id="六、基本图算法"><a href="#六、基本图算法" class="headerlink" title="六、基本图算法"></a>六、基本图算法</h2><h3 id="1-基本概念-2"><a href="#1-基本概念-2" class="headerlink" title="1.基本概念"></a>1.基本概念</h3><ul><li><p>图的定义</p><p>图可以表示为一个二元组$G=\langle V,E\rangle$</p><blockquote><p>相关术语:</p><p>$V$表示非空顶点集,其元素称为顶点(Vertex) ,$\vert V\vert$表示顶点数;</p><p>$E$表示边集,其元素称为边(Edge),$\vert E$表示顶点数 ;</p><p>$e=(u,v)$表示一条边,其中$u\in V,v\in V,e\in E$;</p><p>相邻$(Adjacent)$:边$(u,v)$连接的顶点$u$和$v$相邻;</p><p>关联$(Incident)$:边$(u,v)$和其连接的顶点$u(or,,v)$相互关联。</p></blockquote></li><li><p>相关数据结构</p><ul><li>子图:如果$V’\subseteq V,E’\subseteq E$,则称图$G’=\langle V’,E’\rangle$为G的一个子图</li><li>生成子图:如果$V’=V,E’\subseteq E$,则称图$G’=\langle V’,E’\rangle$为G的一个生成子图</li><li>树:连通、无环图$T=\langle V_T,E_T\rangle$,树有$\vert V_T\vert-1$条边</li><li>森林:一至多棵树组成的无环图</li></ul></li><li><p>图的表示</p><ul><li>邻接表<ul><li>邻接表是一个包含$\vert V\vert$条链表的数组$Adj$;</li><li>在$Adj$中,每个结点$u\in V$有一条链表$Adj[u]$,包含所有与结点$u$之间有边相连的结点$v$;</li><li>用$G.Adj[u]$表示结点$u$在邻接表$Adj$中的邻接链表;</li><li>稀疏图一般用邻接表表示;</li><li>可用于表示有向图也可用于表示无向图,空间需求均为$O(V+E)$。</li></ul></li><li>邻接矩阵<ul><li>将图$G$中的结点编号为$1,2,\dots,\vert V\vert$,则图$G$的邻接矩阵是一个$\vert V\vert\times\vert V\vert$的矩阵$A=(a_{ij})$;</li><li>当$(i,j)\in E$,$a_{ij}=1$;否则$a_{ij}=0$;</li><li>稠密图更倾向于用邻接矩阵表示;</li><li>可以快速判断任意两个结点之间是否有边相连,空间需求为$O(V^2)$。</li></ul></li><li>权重图<ul><li>权重值通常以权重函数$\omega:E\to R$给出;</li><li>用邻接表表示权重图:<ul><li>将边$(u,v)\in E$的权重值$ω(u,v)$存放在$u$的邻接链表结点中, 作为其属性。</li></ul></li><li>用邻接矩阵表示权重图:<ul><li>对于边$(u,v)\in E$,令邻接矩阵$A[u][v]=ω(u,v)$;</li><li>若$(u,v)$不是$E$中的边,则令$A[u][v]=NIL$,或$\infty$、0。</li></ul></li></ul></li></ul></li></ul><h3 id="2-图的搜索与周游"><a href="#2-图的搜索与周游" class="headerlink" title="2.图的搜索与周游"></a>2.图的搜索与周游</h3><h4 id="1-宽度优先搜索与周游"><a href="#1-宽度优先搜索与周游" class="headerlink" title="(1)宽度优先搜索与周游"></a>(1)宽度优先搜索与周游</h4><h5 id="①宽度优先搜索"><a href="#①宽度优先搜索" class="headerlink" title="①宽度优先搜索"></a>①宽度优先搜索</h5><ul><li><p>算法过程描述</p><ul><li>从结点$v$开始,首先访问结点$v$,给$v$标上已访问标记;</li><li>访问邻接于$v$且目前尚未被访问的所有结点,此时结点$v$被检测,而$v$的邻接结点是新的未被检测结点。将这些结点依次放置到一个称为<strong>未检测结点表的队列</strong>(Q)中;</li><li>若未检测结点表为空,则算法终止;</li><li>否则<strong>取Q的表头</strong>作为下一个检测结点,重复上述过程。直到$Q$为空,算法终止。</li></ul></li><li><p>算法伪代码</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-BFS-1.png!blogimg" class="" width="400" title="BFS-1"><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-BFS-2.png!blogimg" class="" width="400" title="BFS-2"></blockquote></li><li><p>复杂度分析</p><ul><li>空间复杂度:$s(V,E)=\Theta(n)$</li><li>采用邻接表的时间复杂度:$t(V,E)=O(n+e)$</li><li>采用邻接矩阵的时间复杂度:$t(V,E)=O(n^2)$</li></ul></li></ul><h5 id="②宽度优先周游"><a href="#②宽度优先周游" class="headerlink" title="②宽度优先周游"></a>②宽度优先周游</h5><ul><li><p>若$G$是无向连通图或强连通有向图,则一次调用$BFS$即可完成对$G$的周游。否则,需要多次调用$BFS$</p></li><li><p>算法伪代码</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-BFT.png!blogimg" class="" width="400" title="BFT"></blockquote></li></ul><h5 id="③宽度优先生成树"><a href="#③宽度优先生成树" class="headerlink" title="③宽度优先生成树"></a>③宽度优先生成树</h5><ul><li><p>向前边:$BFS$中由$u$到达未访问结点$w$的边$(u,w)$称为向前边。</p></li><li><p>宽度优先生成树: 记$T$是$BFS$中处理的所有向前边集合。若$G$是<strong>连通图</strong>,则$BFS$终止时,$T$构成一棵生成树,称为图$G$的宽度优先生成树。</p></li><li><p>对于图$G=(V,E)$和源结点$s$,定义图$G$的<strong>前驱子图</strong>为$G_\pi=(V_\pi,E_\pi)$,其中$V_\pi={v\in V:v.\pi\ne NIL}\cup{s}$,$E_\pi={(v.\pi,v):v\in V_\pi-{s}}$。该前驱子图构成一棵广度优先树。</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-BFS-PRINT-PATH.png!blogimg" class="" width="400" title="BFS-PRINT-PATH"></blockquote></li></ul><h4 id="2-深度优先搜索与周游"><a href="#2-深度优先搜索与周游" class="headerlink" title="(2)深度优先搜索与周游"></a>(2)深度优先搜索与周游</h4><h5 id="①深度优先搜索"><a href="#①深度优先搜索" class="headerlink" title="①深度优先搜索"></a>①深度优先搜索</h5><ul><li><p>算法过程描述</p><ul><li>从结点$v$开始,首先访问$v$, 给$v$标上已访问标记;</li><li>然后中止对$v$的检测,并从邻接于$v$且尚未被访问的结点的中找出一个结点$w$开始新的检测;</li><li>在$w$被检测后,再恢复对$v$的检测。当所有可到达的结点全部被检测完毕后,算法终止</li></ul></li><li><p>算法伪代码</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-DFS.png!blogimg" class="" width="400" title="DFS"></blockquote></li><li><p>复杂度分析</p><ul><li>运行时间为$\Theta(V+E)$</li></ul></li></ul><h5 id="②深度优先周游"><a href="#②深度优先周游" class="headerlink" title="②深度优先周游"></a>②深度优先周游</h5><ul><li><p>算法伪代码</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-DFT.png!blogimg" class="" width="400" title="DFT"></blockquote></li></ul><h4 id="3-深度检索"><a href="#3-深度检索" class="headerlink" title="(3)深度检索"></a>(3)深度检索</h4><ul><li>改造$BFS$算法,用<strong>栈</strong>来保存未被检测的结点</li></ul><h3 id="3-回溯法"><a href="#3-回溯法" class="headerlink" title="3.回溯法"></a>3.回溯法</h3><h4 id="1-n-皇后问题"><a href="#1-n-皇后问题" class="headerlink" title="(1)$n$-皇后问题"></a>(1)$n$-皇后问题</h4><h4 id="2-子集和问题"><a href="#2-子集和问题" class="headerlink" title="(2)子集和问题"></a>(2)子集和问题</h4><h3 id="4-分支-限界法"><a href="#4-分支-限界法" class="headerlink" title="4.分支-限界法"></a>4.分支-限界法</h3><h4 id="1-n-皇后问题-1"><a href="#1-n-皇后问题-1" class="headerlink" title="(1)$n$-皇后问题"></a>(1)$n$-皇后问题</h4><h4 id="2-子集和问题-1"><a href="#2-子集和问题-1" class="headerlink" title="(2)子集和问题"></a>(2)子集和问题</h4><h2 id="七、最小生成树"><a href="#七、最小生成树" class="headerlink" title="七、最小生成树"></a>七、最小生成树</h2><h3 id="1-问题背景"><a href="#1-问题背景" class="headerlink" title="1.问题背景"></a>1.问题背景</h3><ul><li>生成树(Spanning Tree)<ul><li>图$T’=\langle V’,E’\rangle$是无向图$G\langle V,E,W\rangle$的一个生成子图,并且是连通、无环路的(树)</li><li>权重最小的生成树可能不唯一</li></ul></li></ul><h3 id="2-通用框架"><a href="#2-通用框架" class="headerlink" title="2.通用框架"></a>2.通用框架</h3><ul><li><p>新建一个空边集$A$,边集$A$可逐步扩展为最小生成树</p></li><li><p>每次向边集$A$中新增加一条边,需保证边集$A$仍是一个无环图,且仍是最小生成树的子集</p><blockquote><p>$A$是某棵最小生成树$T$边的子集,$A\subseteq T$;</p><p>$A\cup{(u,v)}$仍是$T$边的一个子集,则称$(u,v)$是$A$的安全边。</p><p>若每次向边集$A$中新增安全边,可保证边集$A$是最小生成树的子集</p></blockquote></li><li><p>$Generic-MST(G)$</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp7-GENERIC-MST.png!blogimg" class="" width="400" title="GENERIC-MST"></blockquote></li><li><p>为了有效辨识安全边,给出以下定义</p><blockquote><p>割:对于连通无向图$G=\langle V,E\rangle$,<strong>割</strong>$(S,V-S)$将顶点集$V$划分为两部分</p><p>给定割$(S,V-S)$和边$(u,v)$,$u\in S,v\in V-S$,称边$(u,v)$<strong>横跨</strong>割$(S,V-S)$</p><p>轻边:横跨割的所有边中,<strong>权重最小</strong>的称为横跨这个割的一条轻边</p><p>如果一个边集$A$中<strong>没有边横跨某割</strong>,则称该割不妨害边集$A$</p></blockquote></li><li><p>安全边辨识定理</p><blockquote><p>给定图$G=\langle V,E\rangle$是一个带权的连通无向图,令$A$为边集$E$的一个子集,且$A$包含在图$G$的某棵最小生成树中。若割$(S,V-S)$是图$G$中不妨害边集$A$的任意割,且$(u,v)$是横跨该割的轻边,则对于边集$A$,边$(u,v)$是其安全边。</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-%E5%AE%89%E5%85%A8%E8%BE%B9%E8%BE%A8%E8%AF%86%E5%AE%9A%E7%90%86.png!blogimg" class="" width="400" title="安全边辨识定理"><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-%E5%AE%89%E5%85%A8%E8%BE%B9%E8%BE%A8%E8%AF%86%E5%AE%9A%E7%90%86%E6%8E%A8%E8%AE%BA.png!blogimg" class="" width="400" title="安全边辨识定理推论"></blockquote></li><li><p>在算法推进的过程中,集合$A$始终保持无环状态;算法执行的任意时刻,图$G_A=(V,A)$是一个森林。对于安全边$(u,v)$,由于$A\cup{(u,v)}$必须无环,所以 $(u,v) $连接的是$G_A$中的两个不同连通分量。</p></li></ul><h3 id="3-Prim-算法"><a href="#3-Prim-算法" class="headerlink" title="3.$Prim$算法"></a>3.$Prim$算法</h3><p>贪心策略:集合$A$始终是<strong>一棵树</strong>,每次加入到$A$中的安全边是连接$A$和$A$之外某个结点的边中权重最小的边。</p><ul><li><p><strong>采用的数据结构:最小优先队列</strong>。</p></li><li><p>步骤1:选择任意一个顶点,作为生成树的起始顶点</p></li><li><p>步骤2:保持边集$A$始终为一棵树,选择割$(V_A,V-V_A)$</p></li><li><p>步骤3:选择横跨割$(V_A,V-V_A)$的轻边,添加到边集$A$中</p></li><li><p>步骤4:重复步骤2和步骤3,直至覆盖所有顶点</p></li><li><p>伪代码:</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-MST-PRIM.png!blogimg" class="" width="400" title="MST-PRIM"><p>第$1\thicksim 5$行将每个结点的$key$值设为$\infty$(除了根节点$r$的$key$值为0,以便其为第一个被处理的结点),将每个结点的父节点设为$NIL$,并对最小优先队列$Q$进行初始化;</p><p>在每遍$while$循环前,有</p><ul><li>$A={(v,v.\pi):v\in V-{r}-Q}$</li><li>已经加入到最小生成树的结点为集合$V-Q$</li><li>对于所有结点$v\in Q$,如果$v.\pi\ne NIL$,则$v.key<\infty$且$v.key$是连接结点$v$和最小生成树中某个结点的轻边$(v,v.\pi)$的权重。</li></ul><p>第7行找出结点$u\in Q$,该结点是某条横跨割$(V-Q,Q)$的轻边的一个端点,将$u$从$Q$中删除并将$(u,u.\pi)$加入集合$A$中;</p><p>$for$循环对与$u$相邻却不在树中的结点$v$的属性进行更新。</p></blockquote></li><li><p>优先队列</p><ul><li><p>采用二叉堆实现</p></li><li><p><strong>队列中每个元素有一个关键字,依据关键字大小离开队列</strong></p></li></ul></li></ul><table><thead><tr><th align="center">算法</th><th align="center">说明</th><th align="center">时间复杂度</th></tr></thead><tbody><tr><td align="center">$INSERT()$</td><td align="center">向优先队列中插入元素</td><td align="center">$O(\log n)$</td></tr><tr><td align="center">$EXTRACT-MIN(Q)$</td><td align="center">移除优先队列中的最小元素</td><td align="center">$O(\log n)$</td></tr><tr><td align="center">$DECREASE-KEY(u,u.d)$</td><td align="center">更新距离数组,调整优先队列</td><td align="center">$O(\log n)$</td></tr></tbody></table><ul><li><p>算法复杂度</p><ul><li>算法运行时间取决于$Q$的实现方式,如果实现为二叉最小优先队列,则可以使用$BUILD-MIN-HEAP$执行第$1\thicksim 5$行,时间成本为$O(V)$;</li><li>$while$循环一共执行$\vert V\vert$次,$EXTRACT-MIN$需要时间成本为$O(\lg V)$,$for$循环执行次数为$O(E)$,第11行隐藏$DECREASE-KEY$操作,在二叉最小堆上执行时间成本为$O(\lg V)$;</li><li>总成本为$O(E\lg V)$。</li></ul></li></ul><h3 id="4-Kruskal-算法"><a href="#4-Kruskal-算法" class="headerlink" title="4.$Kruskal$算法"></a>4.$Kruskal$算法</h3><p>贪心策略:集合$A$始终是<strong>一个森林</strong>,开始时,其结点集就是$G$的结点集,并且$A$是所有单节点树构成的森林。之后每次加入到集合$A$中的安全边是$G$中连接$A$的两个不同分量的权重最小的边。</p><ul><li><p><strong>采用的数据结构:不相交集合</strong>。</p></li><li><p>伪代码:</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-MST-KRUSKAL.png!blogimg" class="" width="400" title="MST-KRUSKAL"><p>第$1\thicksim 3$行将$A$初始化为空集,并创建$\vert V\vert$棵树,每棵树只包含一个结点;</p><p>第$5\thicksim 8$行的$for$循环按照权重从低到高的次序对每条边进行检查,如果不在同一棵树中,则加入到集合$A$中,并将两棵树的结点进行合并。</p><p>证明算法保证选择的边为安全边:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-3/Chp6-MST-KRUSKAL-%E5%AE%89%E5%85%A8%E8%BE%B9%E8%AF%81%E6%98%8E.png!blogimg" class="" width="400" title="MST-KRUSKAL-安全边证明"></blockquote></li><li><p>不相交集合</p><ul><li><p>$MAKE-SET(v)$</p><ul><li><p>初始化集合:创建根结点,并设置一条指向自身的边</p></li><li><p>时间复杂度为$O(1)$</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">x.parent=x</span><br><span class="line"><span class="keyword">return</span> x</span><br></pre></td></tr></table></figure></li></ul></li><li><p>$FIND-SET(v)$</p><ul><li><p>判定顶点是否在同一集合:回溯查找树根,检查树根是否相同</p></li><li><p>时间复杂度为$O(h)$,且$\vert V\vert\geq 2^h$,则为$O(\log\vert V\vert)$</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> x.parent ≠ x <span class="keyword">do</span></span><br><span class="line">x=x.parent</span><br><span class="line">end</span><br><span class="line"><span class="keyword">return</span> x</span><br></pre></td></tr></table></figure></li></ul></li><li><p>$UNION(u,v)$</p><ul><li><p>合并两棵树</p></li><li><p>时间复杂度为$O(h)$,且$\vert V\vert\geq 2^h$,则为$O(\log\vert V\vert)$</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">a=FIND-SET(x)</span><br><span class="line">b=FIND-SET(y)</span><br><span class="line"><span class="keyword">if</span> a.height ≤ b.height then</span><br><span class="line"><span class="keyword">if</span> a.height = b.height then</span><br><span class="line">b.height=b.height+<span class="number">1</span></span><br><span class="line">end</span><br><span class="line">a.parent=b</span><br><span class="line">end</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">b.parent=a</span><br><span class="line">end</span><br></pre></td></tr></table></figure></li></ul></li></ul></li><li><p>算法复杂度</p><ul><li>将边按照权重升序排序的时间成本为$O(E\log E)$;</li><li>建立不相交集合的时间成本为$O(V)$;</li><li>$while$循环进行了$\vert E\vert$次,内部时间复杂度为$O(\log V)$,也即$while$循环总时间复杂度为$O(E\log V)$;</li><li>假设$E=O(V^2)$,则总成本为$O(E\lg V)$。</li></ul></li></ul><h3 id="5-算法对比"><a href="#5-算法对比" class="headerlink" title="5.算法对比"></a>5.算法对比</h3><table><thead><tr><th align="center"></th><th align="center">Prim算法</th><th align="center">Kruskal算法</th></tr></thead><tbody><tr><td align="center">核心思想</td><td align="center">保持一颗树,不断扩展</td><td align="center">子树森林,合并为一棵树</td></tr><tr><td align="center">数据结构</td><td align="center">优先队列</td><td align="center">不相交集合</td></tr><tr><td align="center">求解视角</td><td align="center">微观视角,基于当前点选边</td><td align="center">宏观视角,基于全局顺序选边</td></tr><tr><td align="center">算法策略</td><td align="center">采用贪心策略的图算法</td><td align="center">采用贪心策略的图算法</td></tr></tbody></table>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>算法设计与分析笔记(四)</title>
<link href="/posts/2022/08/21/notes/Algorithm-Design-and-Analysis-4/"/>
<url>/posts/2022/08/21/notes/Algorithm-Design-and-Analysis-4/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><div calss='cloudchewie-tag-link'><a class="tag-Link" target="_blank" href="/downloads/Notes-on-Algorithm-Design-and-Analysis.pdf"> <div class="tag-link-tips">站内地址</div> <div class="tag-link-bottom"> <div class="tag-link-left" style="background-image: url(https://picbed.cloudchewie.com/icon/blog-transparent.png!mini);"></div> <div class="tag-link-right"> <div class="tag-link-title">算法设计与分析笔记</div> <div class="tag-link-sitename"> 点击下载算法设计与分析笔记PDF版 </div> </div> <i class="cloudchewiefont cloudchewie-icon-angle-right"></i> </div> </a></div><h2 id="八、单源点最短路问题"><a href="#八、单源点最短路问题" class="headerlink" title="八、单源点最短路问题"></a>八、单源点最短路问题</h2><h3 id="1-问题背景"><a href="#1-问题背景" class="headerlink" title="1.问题背景"></a>1.问题背景</h3><p>给定一个带权重的有向图$G=\langle V,E \rangle$和权重函数$\omega:E\rightarrow R$。图中一条路径$p$的权重$\omega(p)$是构成该路径的所有边的权重之和。从结点$u$到结点$v$的最短路径权重定义为$\delta(u,v)$,当没有从$u$到$v$的路径时,$\delta(u,v)=\infty$。</p><p>试找出从给定的源点$s\in V$到其他每个结点$v\in V$的最短路径及其最短路径。</p><ul><li><p>单源点最短路问题(单目的地最短路径问题,单节点对最短路径问题),所有结点对最短路径问题</p></li><li><p>最优子结构性质:两个结点之间的最短路径的任何子路径都是最短的。</p></li><li><p><strong>松弛操作</strong></p><ul><li>对于每个结点$v$,维持一个属性$v.d$,记录从源点$s$到结点$v$的最短路径权重的上界,称$v.d$为$s$到$v$的最短路径估计</li><li>$INITIALIZE-SINGLE-SOURCE$过程中,对所有$v\in V-{s}$有,$v.d=\infty$,$s.d=0$</li><li>松弛操作中,首先进行测试(对$s$到$v$所经过的最后一个中间结点$u$,比较$v.d$和$u.d+w(u,v)$的值),如果可以<strong>改善</strong>,则更新$v.d$和$v.\pi$</li><li>时间复杂度为$O(1)$</li></ul></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp8-RELAX.png!blogimg" class="" width="400" title="RELAX"><ul><li><p><strong>松弛操作的性质</strong></p><blockquote><p>三角不等式性质:$\delta(s,v)\leq\delta(s,u)+\omega(u,v)$</p><p>上界性质:$v.d$是$s$到$v$的最短路径权重$\delta(s,v)$的上界</p><p>非路径性质:假定从源结点$s$到给定点$v$之间不存在路径,则该图在由算法$INITIALIZE-SINGLE-SOURCE(G,s)$进行初始化后,有$v.d\geq \delta(s,v)=\infty$, 并且该等式作为不变式一直维持到图$G$的所有松弛操作结束。</p></blockquote></li><li><p>若源点$s$无可达负环,则存在源点$s$的单源最短路径(如果有可达负环,则总有更小距离,最终可以松弛到$-\infty$)</p></li></ul><h3 id="2-Bellman-Ford-算法"><a href="#2-Bellman-Ford-算法" class="headerlink" title="2.$Bellman-Ford$算法"></a>2.$Bellman-Ford$算法</h3><ul><li><p>给定带权图$G=\langle V,E,W \rangle$和源点编号$s$,找到源点$s$到所有其他顶点$t$的最短距离$\delta(s,t)$和最短路径$\langle s,\dots,t \rangle$或存在源点$s$可达的负环。</p></li><li><p>解决挑战1:图中存在负权边时,如何求解单源最短路径?</p><ul><li>每轮对所有边进行松弛,持续迭代$\vert V\vert -1$轮。</li></ul></li><li><p>解决挑战2:图中存在负权边时,如何发现源点可达负环?</p><ul><li>若第$\vert V\vert$轮仍松弛成功,存在源点$s$可达的负环。</li></ul></li><li><p>伪代码如下</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp8-BELLMAN-FORD.png!blogimg" class="" width="400" title="BELLMAN-FORD"><p>第1行对所有结点的值进行初始化——$\Theta(V)$;</p><p>第$2\thicksim 4$行对每条边进行$\vert V\vert -1$次松弛处理,每次循环中都对每条边进行一次松弛操作——$\Theta(E)$,共进行$\vert V\vert -1$次循环,总的时间复杂度为$\Theta(VE)$;</p><p>第$5\thicksim 8$行检查图中是否存在权重为负值的环路并返回检查结果——$O(E)$;</p><p><strong>总的运行时间为</strong>$O(VE)$</p></blockquote></li><li><p>证明在<strong>无可达负环</strong>的情况下可以正确计算最短路径权重</p><blockquote><p>设$G=\langle V,E \rangle$为一个带权重的源点为$s$的有向图,其权重函数为$\omega:E\rightarrow R$,并假定图$G$中<strong>不包含</strong>从源结点$s$可以到达的权重为负值的环路。则$Bellman-ford$算法的第$2\thicksim 4$行的$for$循环在执行$\vert V\vert -1$次 之后,对于所有从源结点$s$可以到达的结点$v$有$v.d=\delta(s,v)$。</p><p>证明:<strong>利用路径松弛性质</strong>。</p><p>设$G=\langle V,E \rangle$为一个带权重的源点为$s$的有向图,其权重函数为$\omega:E\rightarrow R$,并假定图$G$中<strong>不包含</strong>从源结点$s$可以到达的权重为负值的环路。则对于所有结点$v\in V$,存在一条从源结点$s$到结点$v$的路径当且仅当$BELLMAN-FORD$算法终止时有$v.d<\infty$。</p></blockquote></li><li><p>证明在<strong>有可达负环</strong>的情况下可以返回FALSE值,否则返回TRUE值</p><blockquote><p>假设不包含可达负环时,可以得到$G_\pi$为一棵最短路径树,则当算法终止时,对于所有边$(u,v)\in E$,有$v.d=\delta(s,v)\leq \delta(s,u)+\omega(u,v)=u.d+\omega(u,v)$,因此返回TRUE;</p><p>假设包含可达负环时,设该环路为$c=\langle v_0,v_1,\dots,v_k \rangle$,其中$v_0=v_k$,则有$\sum\limits_{i=1}^{k}\omega(v_{i-1},v_i)<0$,假设返回TRUE值,则有$v_i.d\leq v_{i-1}.d+\omega(v_{i-1},v_i)$,这里$i=1,2,\dots,k$。将所有不等式相加得到$\sum\limits_{i=1}^{k}v_i.d\leq \sum\limits_{i=1}^{k}(v_{i-1}.d+\omega(v_{i-1},v_i))=\sum\limits_{i=1}^{k}v_{i-1}.d+\sum\limits_{i=1}^{k}\omega(v_{i-1},v_i)$,而$v_0=v_k$,则得到$\sum\limits_{i=1}^{k}v_i.d=\sum\limits_{i=1}^{k}v_{i-1}.d$,又有$\sum\limits_{i=1}^{k}\omega(v_{i-1},v_i)\geq 0$,二者矛盾,得证。</p></blockquote></li></ul><h3 id="3-Dijkstra-算法"><a href="#3-Dijkstra-算法" class="headerlink" title="3.$Dijkstra$算法"></a>3.$Dijkstra$算法</h3><ul><li><p>给定带权图$G=\langle V,E,W \rangle$(所有边的权重为正值)和源点编号$s$,找到源点$s$到所有其他顶点$t$的最短距离$\delta(s,t)$和最短路径$\langle s,\dots,t\rangle$。</p></li><li><p>算法从结点集$V-S$中选择当前最短路径估计最小的结点$u$,将$u$从$Q$中删除,并加入到$S$中,$u.d$就是源结点$s$到$u$的最短路径的 长度。这里$Q$是一个最小优先队列,保存结点集$V-S$。</p></li><li><p>伪代码如下:</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp8-DIJKSTRA.png!blogimg" class="" width="400" title="DIJKSTRA"><p>$while$循环执行次数为$\vert V\vert$次,$for$循环执行$\vert E\vert$次(也即松弛操作次数)。</p></blockquote></li><li><p>证明算法的正确性</p><blockquote><p>采用反证法,假设顶点$u$被添加到$V_A$时,$u.d\ne \delta(s,u)$,而由上界性质有$u.d> \delta(s,u)$。</p><p>应存在一条长度为$\delta(s,u)$的最短路径,设最短路径为$<s,\dots,x,y,\dots,u>$,其中$(x,y)$横跨割$\langle S,V-S\rangle$,$x\in S$,$y\in V-S$;将$x$加入$S$时,有$x.d=\delta(s,x)$,因此$(x,y)$将被松弛,由于$y$是最短路径$p$上的结点,因此有$\delta(s,y)=\delta(s,x)+\omega(x,y)=x.d+\omega(x,y)$,$y.d\leq x.d+\omega(x,y)$,得到$y.d=\delta(s,y)$,因此有$u.d>\delta(s,u)\geq \delta(s,y)=y.d$,显然$u$不是下一个被添加结点,矛盾,得证。</p></blockquote></li><li><p>时间复杂度分析:总运行时间依赖于$Q$的实现,采用二叉堆实现时,每次找到结点$u$需要$O(\lg V)$的时间,总运行时间为$O((V+E)\lg V)$</p></li></ul><h3 id="4-算法对比"><a href="#4-算法对比" class="headerlink" title="4.算法对比"></a>4.算法对比</h3><table><thead><tr><th align="center">项目</th><th align="center">广度优先搜索</th><th align="center">Dijkstra算法</th><th align="center">Bellman-Ford算法</th></tr></thead><tbody><tr><td align="center">适用范围</td><td align="center">无权图</td><td align="center">带权图(所有边权重为正)</td><td align="center">带权图</td></tr><tr><td align="center">松弛次数</td><td align="center">——</td><td align="center">$\vert E \vert$次</td><td align="center">$\vert V\vert\cdot\vert E\vert$次</td></tr><tr><td align="center">数据结构</td><td align="center">队列</td><td align="center">优先队列</td><td align="center">——</td></tr><tr><td align="center">运行时间</td><td align="center">$O(\vert V\vert +\vert E\vert)$</td><td align="center">$O(\vert E\vert\cdot\log\vert V\vert)$</td><td align="center">$O(\vert E\vert\cdot\vert V\vert)$</td></tr></tbody></table><h3 id="5-差分约束系统"><a href="#5-差分约束系统" class="headerlink" title="5.差分约束系统"></a>5.差分约束系统</h3><ul><li><p>线性规划:给定一个$m\times m$的矩阵$A$、一个$m$维的向量$b$和一个$n$维的向量$c$。试找一$n$维向量$x$,使得在$Ax\leq b$的约束下,目标函数$\sum\limits_{i=1}^{n}{c_ix_i}$最大。</p></li><li><p>差分约束系统:矩阵$A$的每一行包括一个1和一个-1,其他所有项均为0。则上述问题转化为$m$个涉及$n$个变量的差额限制条件,每个约束条件均为简单的线性不等关系:$x_j-x_i\leq b_k$,这里$1\leq i,j\leq n,i\ne j,1\leq k\leq m$。</p></li><li><p>解的线性性</p><blockquote><p>$x=(x_1,x_2,\dots,x_n)$为解,则$x+d=(x_1+d,x_2+d,\dots,x_n+d)$也为解;</p></blockquote></li><li><p>约束图</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp8-%E7%BA%A6%E6%9D%9F%E5%9B%BE.png!blogimg" class="" width="400" title="约束图"><p>引入额外结点$v_0$,从其出发可到达任何结点,因此节点集合$V$为${v_0,v_1,\dots,v_n}$</p><p>边集合$E$包含代表每个差分约束的边,同时包含$v_0$到其他所有结点的边$(v_0,v_i)$</p><p>如果$x_j-x_i\leq b_k$是一个差分约束条件,则边$(v_i,v_j)$的权重记为$\omega(v_i,v_j)=b_k$,而从$v_0$出发到其他结点的边的权重$\omega(v_0,v_j)=0$。</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp8-%E7%BA%A6%E6%9D%9F%E5%9B%BE-SAMPLE.png!blogimg" class="" width="400" title="约束图-SAMPLE"></blockquote></li><li><p>问题的转换</p></li></ul><p>给定差分约束系统$Ax\leq b$,设$G=\langle V,E \rangle$是该差分约束系统所对应的约束图。如果图$G$不包含权重为负值的回路,则$x=(\delta(v_0,v_1),\delta(v_0,v_2),\delta(v_0,v_3),\dots,\delta(v_0,v_n))$是该系统的一个可行解。如果图$G$包含权重为负值的回路,则该系统没有可行解。</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp8-%E7%BA%A6%E6%9D%9F%E5%9B%BE%E8%AF%81%E6%98%8E.png!blogimg" class="" width="400" title="约束图证明"><p>设未知变量的个数$n=\vert x_i\vert$,不等式个数为$m$。则使用$Bellman-Ford$算法时,顶点数为$n+1$,边数为$m+n$,因此可以在$O(n^2+mn)$的时间内完成求解。</p><h2 id="九、所有结点对的最短路径问题"><a href="#九、所有结点对的最短路径问题" class="headerlink" title="九、所有结点对的最短路径问题"></a>九、所有结点对的最短路径问题</h2><h3 id="1-问题背景-1"><a href="#1-问题背景-1" class="headerlink" title="1.问题背景"></a>1.问题背景</h3><p>给定一个带权重的有向图$G=\langle V,E \rangle$和权重函数$\omega:E\rightarrow R$。图中一条路径$p$的权重$\omega(p)$是构成该路径的所有边的权重之和。从结点$u$到结点$v$的最短路径权重定义为$\delta(u,v)$,当没有从$u$到$v$的路径时,$\delta(u,v)=\infty$。</p><p>求$\forall u,v\in V$,从$u$到$v$的最短路径。</p><h3 id="2-问题分析"><a href="#2-问题分析" class="headerlink" title="2.问题分析"></a>2.问题分析</h3><ul><li><p>直观上,可以使用$Dijkstra$算法依次求解所有点,此时存在重叠子问题;</p></li><li><p>使用$Dijkstra$算法依次求解所有点的算法复杂度为$O(\vert V\vert\vert E\vert\log\vert V\vert)$,对于稠密图有$\vert E\vert=O(\vert V\vert^2)$,因此算法复杂度为$O(\vert V\vert^3\log\vert V\vert)$;</p></li><li><p>而观察松弛过程发现,具有最优子结构性:</p></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp9-%E6%9C%80%E4%BC%98%E5%AD%90%E7%BB%93%E6%9E%84%E7%A4%BA%E6%84%8F%E5%9B%BE.png!blogimg" class="" width="400" title="最优子结构示意图"><ul><li><p>设$D[k,i,j]$表示:可从前$k$个点选点经过时,$i$到$j$的最短距离,则原始问题为$D[\vert V\vert,i,j]$</p><blockquote><p>如果不选第$k$个点经过,则$D[k,i,j]=D[k-1,i,j]$;</p><p>如果选则第$k$个点经过,则$D[k,i,j]=D[k-1,i,k]+D[k-1,k,j]$;</p><p>因此,$D[k,i,j]=min{D[k-1,i,k]+D[k-1,k,j],D[k-1,i,j]}$</p></blockquote></li></ul><h3 id="3-自底向上的-Floyd-Warshall-算法"><a href="#3-自底向上的-Floyd-Warshall-算法" class="headerlink" title="3.自底向上的$Floyd-Warshall$算法"></a>3.自底向上的$Floyd-Warshall$算法</h3><ul><li><p>初始化数组</p><ul><li>$D[0,i,i]=0$:起点和终点重合,路径长度为0</li><li>$D[0,i,j]=e[i,j]$:任意两点直达距离为边权</li></ul></li><li><p>自底向上计算</p><ul><li>按$k$增加的顺序计算,求解时当前层只依赖上一层</li><li>只需要两层表格——待计算和上一次结果</li><li>当$k=i$或$k=j$时,$D[k,i,j]=D[k-1,i,j]$,可以直接覆盖;</li><li>当$k\ne i且k\ne j$时,$D[k-1,i,k]+D[k-1,k,j]$和$D[k-1,i,j]$不是相同子问题,当求出$D[k,i,j]$后,$D[k-1,i,j]$不再被使用,可直接覆盖——<strong>求出新值可直接在原位置覆盖,只需存储一层表格</strong>;</li></ul></li><li><p>构建最优解</p><ul><li><p>使用前驱结点矩阵记录经过的中间点,此处使用追踪数组$Rec$记录经过的中间点</p><blockquote><p>$D_k[i,j]=D_{k-1}[i,j]$时$Rec$记录为0,表示没有中间点</p><p>$D[k,i,j]=D[k-1,i,k]+D[k-1,k,j]$时$Rec$记录为$k$,表示经过中间点$k$</p></blockquote></li></ul></li><li><p>伪代码如下:</p><blockquote><p>$All-Pairs-Shortest-Paths$:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp9-ALL-PAIRS-SHORTEST-PATHS-1.png!blogimg" class="" width="400" title="ALL-PAIRS-SHORTEST-PATHS-1"><p>初始化数组$D$和$Rec$;</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp9-ALL-PAIRS-SHORTEST-PATHS-2.png!blogimg" class="" width="400" title="ALL-PAIRS-SHORTEST-PATHS-2"><p>按照$k$增大的顺序,对于任意一对$i,j$,进行松弛操作,并更新相关数组。</p><p>$Find-Path$:</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp9-FIND-PATH.png!blogimg" class="" width="400" title="FIND-PATH"></blockquote></li><li><p>算法复杂度为$O(\vert V\vert^3)$</p></li></ul><h3 id="4-最短路径算法小结"><a href="#4-最短路径算法小结" class="headerlink" title="4.最短路径算法小结"></a>4.最短路径算法小结</h3><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp9-%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84%E7%AE%97%E6%B3%95%E5%B0%8F%E7%BB%93.png!blogimg" class="" width="400" title="最短路径算法小结"><h2 id="十、最大流"><a href="#十、最大流" class="headerlink" title="十、最大流"></a>十、最大流</h2><h3 id="1-最大二分匹配"><a href="#1-最大二分匹配" class="headerlink" title="1.最大二分匹配"></a>1.最大二分匹配</h3><h4 id="1-问题描述"><a href="#1-问题描述" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li><p>给定一个无向图$G=\langle V,E \rangle$,其中$V=L\cup R,L\cap R=\Phi$,并且每条边$e\in E$有一个端点在$L$中而另一个端点在$R$中,可记为二分图$G=\langle L,R,E \rangle$。</p></li><li><p>图$G=\langle V,E \rangle$中的一个匹配$M$是图$G$边集$E$的子集$(M\subseteq E)$,其中每个顶点至多关联$M$的一条边。</p></li><li><p>现给定二分图$G=\langle L,R,E \rangle$,求出匹配$M={e_1,e_2,\dots,e_k}$,使得$max\vert M\vert$,满足$\forall i,j(i\ne j),e_i=(l_i,r_i),e_j=(l_j,r_j)$,有$l_i\ne l_j$且$r_i\ne r_j$。</p><blockquote><p>即使得匹配数最大且每个顶点至多关联一条边。</p></blockquote></li></ul><h4 id="2-问题分析-1"><a href="#2-问题分析-1" class="headerlink" title="(2)问题分析"></a>(2)问题分析</h4><ul><li><p>直观上,可以遍历$L$中的顶点,依次检查之并与$R$中顶点进行匹配,这种策略可能达不到最大匹配,需要通过撤销边和连接边来增广原匹配。</p></li><li><p>定义<strong>交替路径</strong>:从未匹配顶点出发,依次经过“非匹配边、匹配边…非匹配边”**形成的路径</p><blockquote><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp10-%E4%BA%A4%E6%9B%BF%E8%B7%AF%E5%BE%84%E4%B8%BE%E4%BE%8B.png!blogimg" class="" width="400" title="交替路径举例"></blockquote></li><li><p>不断寻找交替路径进行增广</p><ul><li>依次检测左侧顶点,若相邻顶点<strong>未匹配</strong>,则构成交替路径,<strong>直接进行匹配</strong>;若相邻顶点<strong>已经匹配</strong>,则尝试寻找交替路径,<strong>增广成新匹配</strong>;</li><li>直至所有左侧顶点检测完后结束。</li></ul></li><li><p>辅助数组</p><ul><li><p>$matched$表示$L$与$R$中顶点的匹配关系</p><blockquote><p>以$R$中顶点作为下标,如$match[R_2]\leftarrow L_1$</p></blockquote></li><li><p>$color$表示深度优先搜索辅助数组</p><ul><li>white表示未被搜索过,black已被搜索过</li><li>每次搜索前初始化$color$数组</li></ul></li></ul></li></ul><h4 id="3-匈牙利算法"><a href="#3-匈牙利算法" class="headerlink" title="(3)匈牙利算法"></a>(3)匈牙利算法</h4><ul><li><p>伪代码如下:</p><blockquote><p>$Hungarian(G)$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp10-Hungarian.png!blogimg" class="" width="400" title="Hungarian"><p>$DFS-Find(v)$</p><img src="https://picbed.cloudchewie.com/blog/post/Algorithm-Design-and-Analysis-4/Chp10-DFS-Find.png!blogimg" class="" width="400" title="DFS-Find"></blockquote></li><li><p>正确性证明:</p><ul><li>命题1:匈牙利算法得到的匹配$M$无交替路径</li><li>命题2:匹配$M$无交替路径$\Leftrightarrow$匹配$M$是最大匹配</li></ul></li></ul><h3 id="2-最大流算法"><a href="#2-最大流算法" class="headerlink" title="2.最大流算法"></a>2.最大流算法</h3><h4 id="1-问题描述-1"><a href="#1-问题描述-1" class="headerlink" title="(1)问题描述"></a>(1)问题描述</h4><ul><li><p>给定有向图$G=\langle V,E,C\rangle$,称之为流网络,$C$代表边权。</p><ul><li><p>源点为$s$,汇点为$t$</p></li><li><p>容量:每条边的边权$c(e)\geq 0$</p></li><li><p>流量:每条边的被占有容量$f(e)\geq 0$</p></li><li><p>剩余容量:对于每条边,剩余容量为$c(e)-f(e)$</p></li><li><p>总流量=源点流出量=汇点流入量:$\vert f\vert=\sum_{e\ out\ of\ s}f(e)=\sum_{e\ in\ to\ t}f(e)$</p></li><li><p>容量限制:对于边$e\in E$,有$0\leq f(e)\leq c(e)$</p><blockquote><p>边上的流量不应超过边上的容量</p></blockquote></li><li><p>流量守恒:对顶点$v\in V-{s,t}$,$\sum_{e\ out\ of\ s}f(e)=\sum_{e\ in\ to\ t}f(e)$</p><blockquote><p>进入某顶点$v$流量和等于流出此顶点流量和</p></blockquote></li></ul></li><li><p>现根据有向图$G=\langle V,E,C\rangle$,源点$s$,汇点$t$,在满足容量限制和流量守恒的约束条件下,求出最大流量。</p></li></ul><h4 id="2-直观策略"><a href="#2-直观策略" class="headerlink" title="(2)直观策略"></a>(2)直观策略</h4><ul><li><p>算法思想</p><ul><li>对于所有边$e\in E$,初始化流量为$f(e)=0$</li><li>寻找一条$s$到$t$的路径$P$,此路径上每条边$e$均满足$f(e)<c(e)$</li><li>按路径$P$上的最小剩余容量增加路径流量</li><li>迭代寻找路径$P$直至无法增加路径流量</li></ul></li><li><p><strong>此方法可能无法达到最大流量</strong></p></li><li><p><strong>不足之处:只能扩充边的流量,不能缩减边的流量</strong></p></li><li><p>如果允许缩减边上的容量 ,则可以进一步增大总流量$\rightarrow$如果寻找路径时允许逆向搜索,可以增大总流量$\rightarrow$引入反向边,实现逆向搜索</p></li><li><p>残存网络</p><ul><li><p>定义反向边权重:可缩减流量的上限,也即原始边上的流量$f(e)$</p></li><li><p>定义正向边权重:可扩充流量的上限,也即原始边上的剩余容量$c(e)-f(e)$</p></li><li><p>则根据流网络$G=\langle V,E,C\rangle$和流量$f$,可得残存网络$G_f=\langle V,E_f\rangle$,其中每条边的残存容量满足上述规则</p></li><li><p>定义增广路径:增广路径$p$是残存网络$G_f$中一条从源点$s$到汇点$t$的简单路径(路径上的各顶点均不互相重复)</p></li><li><p>定义增广路径的残存容量:路径上各边残存容量的最小值</p><blockquote><p>流量扩充的最大值为增广路径的残存容量</p></blockquote></li></ul></li></ul><h4 id="3-Ford-Fulkerson-算法"><a href="#3-Ford-Fulkerson-算法" class="headerlink" title="(3)$Ford-Fulkerson$算法"></a>(3)$Ford-Fulkerson$算法</h4><ul><li>算法思想<ul><li>对于所有边$e\in E$,初始化流量为$f(e)=0$</li><li>构造残存网络$G_f$,寻找$s$到$t$的增广路径$P$</li><li>按路径$P$的<strong>残存容量</strong>增加路径流量</li><li>迭代寻找路径$P$直至无法增加路径流量</li></ul></li><li>伪代码如下</li><li>算法复杂度</li><li>正确性证明<ul><li>充分性:$f$是最大流$\Rightarrow$残存网络$G_f$中无增广路径</li><li>必要性:$f$是最大流$\Leftarrow$残存网络$G_f$中无增广路径</li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 算法 </tag>
</tags>
</entry>
<entry>
<title>计算机组成原理笔记(一)</title>
<link href="/posts/2022/06/12/notes/Principles-of-Computer-Composition-1/"/>
<url>/posts/2022/06/12/notes/Principles-of-Computer-Composition-1/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="一、计算机系统概述"><a href="#一、计算机系统概述" class="headerlink" title="一、计算机系统概述"></a>一、计算机系统概述</h2><h3 id="1-冯诺依曼体系结构"><a href="#1-冯诺依曼体系结构" class="headerlink" title="1.冯诺依曼体系结构"></a>1.冯诺依曼体系结构</h3><ul><li><p>由运算器、控制器、存储器、输入设备和输出设备五部分组成</p></li><li><p>图解如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-1/Chp1-%E5%86%AF%E8%AF%BA%E4%BE%9D%E6%9B%BC%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="冯诺依曼体系结构"></li></ul><h3 id="2-计算机系统的组成"><a href="#2-计算机系统的组成" class="headerlink" title="2. 计算机系统的组成"></a>2. 计算机系统的组成</h3><ul><li>硬件系统组成<ul><li><strong>存储器</strong>:存放程序和数据(以二进制形式存放),按地址访问;</li><li><strong>运算器</strong>:执行算术运算和逻辑运算;</li><li><strong>控制器</strong>:根据<strong>指令的操作码、指令执行过程中的条件状态、时序系统</strong>等三方面的因素来产生指令执行过程中所需要的控制信号,控制数据的存取和程序的执行;</li><li><strong>输入设备</strong>:将信息输入到计算机的外部设备,如键盘、鼠标等;</li><li><strong>输出设备</strong>:输出计算机处理结果的外部设备。如显示器、打印机等。</li></ul></li><li>软件系统组成<ul><li>应用软件:解决应用问题的程序集合,如数据处理程序、情报检索程序等;</li><li>系统软件:管理和调度计算机,以方便用户使用计算机并提高计算机使用效率的程序的集合,包括:<ul><li>操作系统</li><li>程序设计语言处理程序:编译器,汇编器,解释器</li><li>数据库管理系统</li></ul></li></ul></li></ul><h3 id="3-计算机的性能指标"><a href="#3-计算机的性能指标" class="headerlink" title="3.计算机的性能指标"></a>3.计算机的性能指标</h3><h4 id="1-基本性能指标"><a href="#1-基本性能指标" class="headerlink" title="(1)基本性能指标"></a>(1)基本性能指标</h4><ul><li>字长:$CPU$一次处理的数据位数,一般<strong>与计算机内部寄存器、运算器、数据总线的位宽相等</strong>,影响计算精确度和数据的表示范围与精度;</li><li>主存容量:主存能存储的最大信息量,由$\underline{M\times N}$表示,其中$M$表示字容量(<strong>存储单元数</strong>),$N$表示位容量(<strong>每个存储单元的二进制位数</strong>)。</li></ul><h4 id="2-与时间有关的性能指标"><a href="#2-与时间有关的性能指标" class="headerlink" title="(2)与时间有关的性能指标"></a>(2)与时间有关的性能指标</h4><ul><li><p>时钟周期:时钟频率(<u><strong>主频</strong></u>)的导数,是计算机处理操作最基本的时间单位;</p></li><li><p>$CPI$:执行每条指令所需的平均时钟周期数;</p><blockquote><p>约定$IC$表示所有指令的总条数,$m$表示程序执行所需时钟周期数,$P_{i}$表示某类指令的使用频率,$IC_{i}$表示某类指令的条数,则满足$CPI=\frac{m}{IC}=\sum\limits_{i=1}^{n}(CPI_{i}\times P_{i})=\sum\limits_{i=1}^{n}(CPI_{i}\times \frac{IC_{i}}{IC}).$</p></blockquote></li><li><p>$IPC$:每个时钟周期$CPU$能执行的指令条数;</p><blockquote><p>$IPC$满足:$IPC=\frac{1}{CPI}.$</p></blockquote></li><li><p>$CPU$时间:程序执行期间真正消耗$CPU$的时间(包括用户$CPU$时间和系统$CPU$时间);</p><blockquote><p>约定$T$表示时钟周期时长,$f$表示$CPU$主频,则某段程序$CPU$时间可表示为$T_{cpu}=m\times T=\frac{m}{f}=CPI\times IC\times T=\frac{CPI\times IC}{f}.$</p></blockquote></li><li><p>$MIPS$:每秒钟执行的百万条指令数;</p><blockquote><p>约定$f’=f\times 10^{6}$,则$MIPS=\frac{IC}{T_{cpu}\times 10^{6}}=\frac{f}{CPI\times 10^{6}}=IPC\times f’.$</p></blockquote></li><li><p>$MFLOPS$:每秒钟执行的浮点运算次数。</p></li></ul><h3 id="4-计算机系统的层次结构"><a href="#4-计算机系统的层次结构" class="headerlink" title="4.计算机系统的层次结构"></a>4.计算机系统的层次结构</h3><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-1/Chp1-%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="计算机系统的层次结构"><div style="page-break-after:always;"></div><h2 id="二、数据信息的表示"><a href="#二、数据信息的表示" class="headerlink" title="二、数据信息的表示"></a>二、数据信息的表示</h2><h3 id="1-数值数据的表示"><a href="#1-数值数据的表示" class="headerlink" title="1.数值数据的表示"></a>1.数值数据的表示</h3><h4 id="1-数值与机器码"><a href="#1-数值与机器码" class="headerlink" title="(1)数值与机器码"></a>(1)数值与机器码</h4><ul><li>数据格式是指<strong>使用二进制编码表示实际数据</strong>的结构形式,分类如下:</li></ul><table><thead><tr><th align="center">分类依据</th><th align="center">具体分类</th></tr></thead><tbody><tr><td align="center">是否有符号位</td><td align="center">无符号数和有符号数</td></tr><tr><td align="center">小数点位置</td><td align="center">定点数和浮点数</td></tr></tbody></table><ul><li><p>定点数和浮点数比较如下:</p><ul><li>定点数:包括定点整数和定点小数<ul><li>定点小数:小数点位置在最高数位之前(符号位之后)</li><li>定点小数:小数点位置在最低数位之后</li><li>定点整数存在<strong>上溢</strong>问题(超出表示范围)</li><li>定点小数存在<strong>精度溢出</strong>问题(超出表示精度)</li></ul></li><li>浮点数<ul><li>表示方法:两个<u><strong>定点数</strong></u>分别表示<strong>阶码和尾数</strong></li><li>溢出问题:存在上溢和下溢问题,也存在精度溢出问题</li><li>数据分布:浮点数在数轴上的分布并不均匀,<strong>越远离原点,浮点数越稀疏</strong></li><li>浮点运算<u>不满足结合律</u>,小数+大数=大数</li></ul></li></ul></li><li><p>真值与机器码比较如下:</p></li></ul><table><thead><tr><th align="center"></th><th align="center">表示形式</th><th align="center">机器零</th><th align="center">用途</th></tr></thead><tbody><tr><td align="center">真值</td><td align="center">用”$+$”和“$-$”表示符号</td><td align="center">无</td><td align="center">无</td></tr><tr><td align="center">原码</td><td align="center">将符号位加上二进制数的绝对值</td><td align="center">$+0=000$$-0=100$</td><td align="center">表示浮点数的尾码</td></tr><tr><td align="center">反码</td><td align="center">符号位同原码,真值为负数时数值位逐位取反</td><td align="center">$+0=000$$-0=111$</td><td align="center">无</td></tr><tr><td align="center">补码</td><td align="center">真值为负时反码末位加一得到补码</td><td align="center">$0=000$</td><td align="center">计算机中采用补码进行存储、表示和运算</td></tr><tr><td align="center">移码</td><td align="center">与补码的符号位相反,数值位相同</td><td align="center">$0=100$</td><td align="center">表示浮点数的阶码</td></tr></tbody></table><ul><li><p>有关补码:</p><ul><li>补码又称为<strong>模2的补码</strong>,定点小数的模值为$2$,定点整数的模值为$2^{n+1}$;</li><li>补码的机器零唯一,<strong>多表示一个绝对值最大的负数</strong>,小数为$-1$,整数为$-2^n$;</li><li>反码法<ul><li><strong>真值为负数</strong>时将原码数据位逐位取反后末位加一得到补码;</li><li><strong>补码符号位为1</strong>时将补码数据位逐位取反后末位加一得到原码;</li></ul></li><li>扫描法<ul><li><strong>真值为负数</strong>时对原码数据位从右向左扫描,右起第一个1及其右边的数据位不变,其余各位取反。</li><li><strong>补码符号位为1</strong>时对原码数据位从右向左扫描,右起第一个1及其右边的数据位不变,其余各位取反。</li></ul></li></ul></li><li><p>有关变形补码:</p><ul><li>又称为双符号补码,指采用<u><strong>两个符号位</strong></u>表示数据的符号,其余位与补码相同;</li><li>当符号位为$00$时表示正数,为$11$时表示负数;</li><li>在运算时,即使产生溢出,变形补码的<u><strong>最高位</u><strong>也</strong><u>永远</strong></u>表示正确的符号位;</li><li>符号位为$01$表示运算出现正溢出,为$10$时表示出现负溢出。</li></ul></li></ul><h4 id="2-定点数表示"><a href="#2-定点数表示" class="headerlink" title="(2)定点数表示"></a>(2)定点数表示</h4><ul><li>数据表示范围</li></ul><table><thead><tr><th></th><th>最大正数</th><th>最小正数</th><th>最大负数</th><th>最小负数</th></tr></thead><tbody><tr><td>定点小数</td><td>$1-2^{-n}$</td><td>$2^{-n}$</td><td>$-2^{-n}$</td><td>$-(1-2^{-n})$,补码表示时为$-1$</td></tr><tr><td>定点整数</td><td>$2^n-1$</td><td>$1$</td><td>$-1$</td><td>$-(2^n-1)$,补码表示时为$-2^n$</td></tr></tbody></table><ul><li>机器码计算公式</li></ul><table><thead><tr><th align="center">机器码</th><th align="center">$-2^n<x\leq0$(定点整数)</th><th align="center">$-1<x\leq0$(定点小数)</th><th align="center">$0\leq x\leq 2^n-1$或$0\leq x\leq 1-2^{-n}$</th></tr></thead><tbody><tr><td align="center">原码</td><td align="center">$2^n+</td><td align="center">x</td><td align="center">$</td></tr><tr><td align="center">反码</td><td align="center">$2^{n+1}+x-1$</td><td align="center">$2+x-2^{-n}$</td><td align="center">$x$</td></tr><tr><td align="center">补码</td><td align="center">$2^{n+1}+x$</td><td align="center">$2+x$</td><td align="center">$x$</td></tr><tr><td align="center">移码</td><td align="center">$2^n+x$</td><td align="center">无</td><td align="center">$2^n+x$(整数)</td></tr></tbody></table><h4 id="3-浮点数表示"><a href="#3-浮点数表示" class="headerlink" title="(3)浮点数表示"></a>(3)浮点数表示</h4><ul><li><p>表示规则</p><ul><li>$N=2^{E}\times M=2^E\times\pm(1.m)$</li><li>$IEEE754$规则下,浮点数由数符$S$、阶码$E$、尾数$M$三部分组成,阶码采用<u>移码</u>表示,尾数采用<u>原码</u>表示;</li><li>尾数为定点小数,小数点固定在最左侧,且隐藏小数点左边的$1$,运算时还原为$1.M$形式;</li><li>对于$32$位浮点数($float$)而言:$S$为$1$位,$E$为$8$位,移码偏移为$2^7-1$,$M$为$23$位;</li><li>对于$64$位浮点数($double$)而言:$S$为$1$位,$E$为$11$位,移码偏移为$2^{10}-1$,$M$为$52$位。</li></ul></li><li><p>转换方法(以$32$位浮点数$float$为例)</p><ul><li>将十进制数$N$转换为$(-1)^s\times 2^e\times 1.M$,令$E=e+01111111$,保存$S$、$E$、$M$;</li><li>从$32$位二进制串分离出$S$、$E$、$M$,令$e=E-01111111$,代入$(-1)^s\times 2^e\times 1.M$,按权展开。</li></ul></li><li><p>具体形式说明</p><ul><li>当阶码为$1\sim 254$时,表示规格化数据;</li><li>当阶码为$255$时,表示非数或者$\infty$;</li><li>当阶码为$0$时,表示机器零或者非规格化数。</li></ul></li></ul><table><thead><tr><th align="center">符号位$S$</th><th align="center">阶码$E$</th><th align="center">尾数$M$</th><th align="center">表示</th></tr></thead><tbody><tr><td align="center">$0/1$</td><td align="center">$255$</td><td align="center">非零</td><td align="center">$NaN$</td></tr><tr><td align="center">$0$</td><td align="center">$255$</td><td align="center">$0$</td><td align="center">$+\infty$</td></tr><tr><td align="center">$1$</td><td align="center">$255$</td><td align="center">$0$</td><td align="center">$-\infty$</td></tr><tr><td align="center">$0/1$</td><td align="center">$1\sim 254$</td><td align="center">$M$</td><td align="center">$(-1)^s\times 2^{E-127}\times 1.M$</td></tr><tr><td align="center">$0/1$</td><td align="center">0</td><td align="center">$M$(非零)</td><td align="center">$(-1)^s\times 2^{-127}\times 0.M$(非规格化数)</td></tr><tr><td align="center">$0/1$</td><td align="center">$0$</td><td align="center">$0$</td><td align="center">$+0/-0$</td></tr></tbody></table><h3 id="2-非数值数据的表示"><a href="#2-非数值数据的表示" class="headerlink" title="2.非数值数据的表示"></a>2.非数值数据的表示</h3><h4 id="1-字符表示"><a href="#1-字符表示" class="headerlink" title="(1)字符表示"></a>(1)字符表示</h4><p>$ASCII$码是国际通用的字符码,包含$128$个字符,用<strong>一个字节</strong>表示,最高位为<u>零</u>。</p><h4 id="2-汉字编码"><a href="#2-汉字编码" class="headerlink" title="(2)汉字编码"></a>(2)汉字编码</h4><ul><li><p>汉字编码包含<strong>输入码、机内码和字形码</strong>,分别用于汉字的输入、汉字在计算机内的存储与处理、汉字的显示和打印;</p></li><li><p>汉字<u>机内码</u>主要包括$GB2312$、$GBK$、$GB18030$、$Unicode$、$B1G5$等标准。</p></li><li><p>$GB2312$编码</p><ul><li><p>以$2$<strong>个字节</strong>编码,最高位$MSB$为$1$;</p></li><li><p>实际用$14$位表示汉字,采用$94\times94$矩阵表示,每一行为区号,每一列为位号,采用<u><strong>区号+位号</strong></u>的方式得到区位码。</p></li><li><p>$GB2312$机内码$=$区位码$+A0A0H$</p></li></ul></li></ul><h3 id="3-数据信息的校验"><a href="#3-数据信息的校验" class="headerlink" title="3.数据信息的校验"></a>3.数据信息的校验</h3><h4 id="1-码距"><a href="#1-码距" class="headerlink" title="(1)码距"></a>(1)码距</h4><ul><li><p>码距:两个编码对应位二进制位不同的个数。</p><ul><li>编码体系的码距:一个编码体系中所有合法编码的最小码距;</li><li>码距越大,抗干扰能力、纠错能力越强,数据冗余越大,编码效率越低。</li></ul></li><li><p>校验码:原始数据$+$校验数据</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-1/Chp2-%E6%95%B0%E6%8D%AE%E6%A0%A1%E9%AA%8C%E7%9A%84%E5%BA%94%E7%94%A8%E6%A8%A1%E5%BC%8F.png!blogimg" class="" width="400" title="数据校验的应用模式"></li></ul><h4 id="2-奇偶校验"><a href="#2-奇偶校验" class="headerlink" title="(2)奇偶校验"></a>(2)奇偶校验</h4><ul><li>编码规则:增加一位校验位,使得数据中的$1$的个数保持奇偶性,利用编码中$1$的个数的奇偶性进行校验;</li><li>检错性能:奇偶校验码距为$2$,只能检测<strong>奇数位</strong>错误;</li><li>可采用交叉奇偶校验提高奇偶校验码的检错与纠错能力,交叉奇偶校验可以检测出所有的$3$位以下错误以及大多数$4$位错误。</li><li>简单奇偶校验公式:<ul><li>偶校验位:$P=D_{1}\oplus D_{2}\cdots\oplus D_{n}$</li><li>偶校验检错位:$G=D_{1}’\oplus D_{2}’\cdots\oplus D_{n}’\oplus P’$</li><li>奇校验位:$P=\overline{D_{1}\oplus D_{2}\cdots\oplus D_{n}}$</li><li>奇校验检错位:$G=\overline{D_{1}’\oplus D_{2}’\cdots\oplus D_{n}’\oplus P’}$</li></ul></li><li>交叉奇偶校验<ul><li>交叉奇偶校验将待编码的原始数据构造成行列矩阵式结构,同时进行行和列两个方向上的奇偶校验;</li><li>使<strong>每个数据至少位于两个以上的校验组</strong>,当校验码中的某一位发生错误时,能在多个检错位中被指出,使得<strong>偶数位错误也可以被检查出</strong>。</li></ul></li></ul><h4 id="3-海明校验"><a href="#3-海明校验" class="headerlink" title="(3)海明校验"></a>(3)海明校验</h4><h5 id="①海明编码概述"><a href="#①海明编码概述" class="headerlink" title="①海明编码概述"></a>①海明编码概述</h5><ul><li>海明编码又称为$SEC$码;</li><li>$SEC$码的码距为$3$,只能纠一位错;</li><li>扩展海明码的码距为$4$,可以检测两位错同时纠正一位错;</li><li>海明校验是本质上是一种<strong>多重奇偶校验</strong>,<strong>既可以检错也可以纠错</strong>。</li><li>将原始数据信息分成若干偶奇偶校验组,所有数据信息位都会参与两个以上的校验组,每组设置一位偶校验位,所有校验组检错位的值构成检错码;</li><li>检错码为零,表示数据<u>高概率</u>正确,检错码不为零,则检错码值就是一位错的位置,通过检错码的值就可以实现编码的纠错。</li></ul><h5 id="②校验位的位数"><a href="#②校验位的位数" class="headerlink" title="②校验位的位数"></a>②校验位的位数</h5><ul><li><p>设海明码$N$位,其中数据位$k$位,校验位$r$位,有$N=k+r$,称为$(n,k)$码;</p></li><li><p>海明码包含$r$个偶校验组,$r$个偶校验组的$r$检错信息构成一个检错码$G_r\cdots G_2G_1$;</p></li><li><p>为了使其能指出所有一位错,应有$N=k+r\leq 2^r-1$,这便是常见的$ECC$纠错码。</p></li></ul><h5 id="③编码分组规则"><a href="#③编码分组规则" class="headerlink" title="③编码分组规则"></a>③编码分组规则</h5><ul><li><p>设有海明码$H_n\cdots H_2H_1$,原始数据$D_k\cdots D_2D_1$,校验位$P_r\cdots P_2P_1$;</p></li><li><p>为满足编码分组要求,校验位$P_i$放在$H_{2^{i-1}}$位置上,剩余位置由数据位依次填充;</p></li><li><p>$H_i$的数据被编号小于$i$的若干个海明码位号之和等于$i$的校验位所校验;</p><p>如对于$(11,7)$码,其编码分组如下:</p></li></ul><table><thead><tr><th align="center">$H_i$</th><th align="center">1</th><th align="center">2</th><th align="center">3</th><th align="center">4</th><th align="center">5</th><th align="center">6</th><th align="center">7</th><th align="center">8</th><th align="center">9</th><th align="center">10</th><th align="center">11</th></tr></thead><tbody><tr><td align="center">映射</td><td align="center">$P_1$</td><td align="center">$P_2$</td><td align="center">$D_1$</td><td align="center">$P_3$</td><td align="center">$D_2$</td><td align="center">$D_3$</td><td align="center">$D_4$</td><td align="center">$P_4$</td><td align="center">$D_5$</td><td align="center">$D_6$</td><td align="center">$D_7$</td></tr><tr><td align="center">分组</td><td align="center">$1$</td><td align="center">$2$</td><td align="center">$1,2$</td><td align="center">$4$</td><td align="center">$1,4$</td><td align="center">$2,4$</td><td align="center">$1,2,4$</td><td align="center">$8$</td><td align="center">$1,8$</td><td align="center">$2,8$</td><td align="center">$1,2,8$</td></tr></tbody></table><ul><li><p>由此根据偶校验规则和各个校验位所校验的数据位,可以得到校验位计算公式:</p><p>$P_1=D_1\oplus D_2\oplus D_4\oplus D_5\oplus D_7$</p><p>$P_2=D_1\oplus D_3\oplus D_4\oplus D_6\oplus D_7$</p><p>$P_3=D_2\oplus D_3\oplus D_4$</p><p>$P_4=D_5\oplus D_6\oplus D_7$</p></li><li><p>同样可以得到检错位:</p><p>$G_1=D_1’\oplus D_2’\oplus D_4’\oplus D_5’\oplus D_7’\oplus P_1’$</p><p>$G_2=D_1’\oplus D_3’\oplus D_4’\oplus D_6’\oplus D_7’\oplus P_2’$</p><p>$G_3=D_2’\oplus D_3’\oplus D_4’\oplus P_3’$</p><p>$G_4=D_5’\oplus D_6’\oplus D_7’\oplus P_4’$</p></li></ul><h5 id="⑤检错与纠错"><a href="#⑤检错与纠错" class="headerlink" title="⑤检错与纠错"></a>⑤检错与纠错</h5><ul><li>当检错码$G_r\cdots G_2G_1=0$时,表示海明码大概率正确(当出错位数大于等于最小码距时,检错码也可以为$0$);</li><li>$SEC$码无法区分一位错和两位错;</li><li>当出现一位错时,检错码的值对应出错的海明码位号,直接取反即可纠错。</li></ul><h5 id="⑥扩展海明码"><a href="#⑥扩展海明码" class="headerlink" title="⑥扩展海明码"></a>⑥扩展海明码</h5><ul><li>又称为$SECDED$码,最小码距为4,可以区分一位错和两位错,并能纠一位错;</li><li>在$SEC$码的基础上添加总偶校验位$P_{all}=(D_1\oplus D_2\cdots\oplus D_k)\oplus(P_1\oplus P_2\cdots\oplus P_k)$;</li><li>总偶校验检错码$G_{all}=P_{all}’\oplus(D_1’\oplus D_2’\cdots\oplus D_k’)\oplus(P_1’\oplus P_2’\cdots\oplus P_k’)$;</li><li>检错方法:<ul><li>$G_{all}=0$且$G=0$时,无错误发生;</li><li>$G_{all}=1$时,出现一位错,此时如果$G=0$,说明$P_{all}$发生错误,数据部分正确,如果$G\ne 0$,说明数据部分发生一位错,可以根据检错码进行纠错;</li><li>$G_{all}=0$且$G\ne 0$时,出现两位错。</li></ul></li></ul><h4 id="4-CRC校验"><a href="#4-CRC校验" class="headerlink" title="(4)CRC校验"></a>(4)CRC校验</h4><h5 id="①编码规则"><a href="#①编码规则" class="headerlink" title="①编码规则"></a>①编码规则</h5><ul><li>利用模$2$运算增加若干位校验位,使得该编码能够被指定的多项式整除;</li></ul><table><thead><tr><th align="center">模$2$运算</th><th align="left">运算法则</th></tr></thead><tbody><tr><td align="center">加减法</td><td align="left">没有进位和借位的二进制加法和减法运算</td></tr><tr><td align="center">乘法</td><td align="left">根据模$2$加法运算求部分积之和,运算过程中不考虑进位</td></tr><tr><td align="center">除法</td><td align="left">根据模$2$减法求部分余数</td></tr></tbody></table><blockquote><p> 除法运算法则:</p><ul><li>部分余数首位为$1$时,商上$1$,按模$2$运算减除数;</li><li>部分余数首位为$0$时,商上$0$,减$0$;</li><li>部分余数小于除数的位数时,该余数即为最后余数;</li></ul></blockquote><h5 id="②-CRC-校验流程"><a href="#②-CRC-校验流程" class="headerlink" title="②$CRC$校验流程"></a>②$CRC$校验流程</h5><p>设有$CRC$码$N$位,原始数据$C_{k-1}\cdots C_1C_0$共$k$位,校验位$P_{r-1}\cdots P_1P_0$共$r$位,则$CRC$码为$C_{k-1}\cdots C_1C_0P_{r-1}\cdots P_1P_0$,称为$(n,k)$码,满足$N=k+r\leq 2^r-1$。</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-1/Chp2-CRC%E6%A0%A1%E9%AA%8C%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="CRC校验流程"><h5 id="③生成-CRC-编码"><a href="#③生成-CRC-编码" class="headerlink" title="③生成$CRC$编码"></a>③生成$CRC$编码</h5><ul><li><p>假设待发送的$k$位二进制数据用信息多项式$M(x)$表示:$M(x)=C_{k-1}x^{k-1}+C_{k-2}x^{k-2}+\cdots+C_{1}x+C_{0}$;</p></li><li><p>将$M(x)$左移$r$位,得到$M(x)\cdot 2^r$,右侧空置的$r$位用来放置校验位;</p></li><li><p>选择一个$r+1$位的生成多项式$G(x)$,其最高次幂为$r$,最低次幂为$0$;</p></li><li><p>用$M(x)\cdot 2^r$按照模$2$的规则除以$G(x)$,得到的余数$R(x)$即为校验码;</p><blockquote><p>设商为$Q(x)$,则有$M(x)\cdot 2^r+R(x)=Q(x)G(x)+R(x)+R(X)$,根据模$2$运算有$R(x)+R(x)=0$,因此$M(x)\cdot 2^r+R(x)=Q(x)G(x)$,表明$CRC$码一定能被$G(x)$整除,这也是$CRC$码的编码规则。</p></blockquote></li><li><p>生成多项式的规则</p><ul><li>最高位和最低位均为$1$;</li><li>当$CRC$码任何一位发生错误时,都不能被生成多项式整除;</li><li>不同位发生错误时,余数不同;</li><li>对余数继续做模$2$运算,应使余数循环。</li></ul></li></ul><h5 id="④-CRC-编码的循环特性"><a href="#④-CRC-编码的循环特性" class="headerlink" title="④$CRC$编码的循环特性"></a>④$CRC$编码的循环特性</h5><ul><li>$CRC$编码的非$0$余数具有循环特性,即<strong>将余数左移一位除以生成多项式,将得到下一个余数</strong>,继续重复在新余数基础上左移一位除以生成多项式,多次循环后余数最终能循环为最开始的余数;</li><li>例如对于$(7,3)$码,设生成多项式为$11101$,数据位为$3$位,校验码为$4$位,则余数表如下所示:</li></ul><table><thead><tr><th align="center">序号</th><th align="center">编码</th><th align="center">余数</th><th align="center">余数值</th><th align="center">出错位</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">$0000000$</td><td align="center">$0000$</td><td align="center">$0$</td><td align="center">无</td></tr><tr><td align="center">2</td><td align="center">$000000\pmb{\underline{1}}$</td><td align="center">$0001$</td><td align="center">$1$</td><td align="center">$1$</td></tr><tr><td align="center">3</td><td align="center">$00000\pmb{\underline{1}}0$</td><td align="center">$0010$</td><td align="center">$2$</td><td align="center">$2$</td></tr><tr><td align="center">4</td><td align="center">$0000\pmb{\underline{1}}00$</td><td align="center">$0100$</td><td align="center">$\pmb{\underline{4}}$</td><td align="center">$3$</td></tr><tr><td align="center">5</td><td align="center">$000\pmb{\underline{1}}000$</td><td align="center">$1000$</td><td align="center">$8$</td><td align="center">$4$</td></tr><tr><td align="center">6</td><td align="center">$00\pmb{\underline{1}}0000$</td><td align="center">$1101$</td><td align="center">$13$</td><td align="center">$5$</td></tr><tr><td align="center">7</td><td align="center">$0\pmb{\underline{1}}00000$</td><td align="center">$0111$</td><td align="center">$7$</td><td align="center">$6$</td></tr><tr><td align="center">8</td><td align="center">$\pmb{\underline{1}}000000$</td><td align="center">$1110$</td><td align="center">$14$</td><td align="center">$7$</td></tr><tr><td align="center">9</td><td align="center">$00000\pmb{\underline{11}}$</td><td align="center">$0011$</td><td align="center">$3$</td><td align="center">$1+2$</td></tr><tr><td align="center">10</td><td align="center">$\pmb{\underline{111}}0000$</td><td align="center">$0100$</td><td align="center">$\pmb{\underline{4}}$</td><td align="center">$5+6+7$</td></tr></tbody></table><h5 id="⑤-CRC-串行编解码"><a href="#⑤-CRC-串行编解码" class="headerlink" title="⑤$CRC$串行编解码"></a>⑤$CRC$串行编解码</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-1/Chp2-CRC%E4%B8%B2%E8%A1%8C%E7%BC%96%E8%A7%A3%E7%A0%81.png!blogimg" class="" width="400" title="CRC串行编解码"><ul><li>触发器的初始状态均为$0$;</li><li>有异或门的位置为生成多项式为$1$的位置,如图例得到$G(x)=x^4+x+1$,对应编码为$10011$;</li><li>当$Q_4=0$时,不够除,异或门相当于直通,下一个时钟时,数据左移一位;</li><li>当$Q_4=1$时,够除,商上$1$,进行$Q_4Q_3Q_2Q_1Serial_in\oplus G(x)$,结果左移。</li></ul><h5 id="⑥-CRC-并行编解码"><a href="#⑥-CRC-并行编解码" class="headerlink" title="⑥$CRC$并行编解码"></a>⑥$CRC$并行编解码</h5><ul><li><p>模$2$除法余数运算满足结合律:<strong>两数的余数异或等于两数异或后的余数</strong></p><p>$(M(x)%G(x))\oplus(N(x)%G(x))=(M(x)\oplus N(x))%G(x)$</p></li><li><p>例如:设生成多项式$G(x)=1011$,原始数据为$M(x)=1101$,传输后的编码为$1101\underline{011}$,试求$CRC$编码和传输后的余数:</p><ul><li><strong>发送方编码</strong>:$1101\underline{000}=\pmb{1}000\underline{000}\oplus0\pmb{1}00\underline{000}\oplus000\pmb{1}\underline{000}$,则余数可以由三个数分别对$G(x)$进行模$2$运算后的余数异或得到;</li><li><strong>接收方解码</strong>:$1101\underline{011}=\pmb{1}000\underline{000}\oplus0\pmb{1}00\underline{000}\oplus000\pmb{1}\underline{000}\oplus0000\underline{011}$,同样可以得到对应的余数,其中$0000\underline{011}$对$G(x)$进行模$2$运算后的余数即为$\underline{011}$;</li></ul></li><li><p>计算流程</p><ul><li>先计算$2^6$、$2^5$、$2^4$、$2^3$四个特殊常量的余数,再用余数的组合求解任意编码的余数。</li><li>在解码时,将计算得到的余数与各个特殊常量的余数比较,若相等,则该特殊常量对应的位出错,纠错即可。</li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机组成原理 </tag>
</tags>
</entry>
<entry>
<title>计算机组成原理笔记(二)</title>
<link href="/posts/2022/06/12/notes/Principles-of-Computer-Composition-2/"/>
<url>/posts/2022/06/12/notes/Principles-of-Computer-Composition-2/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="三、运算器"><a href="#三、运算器" class="headerlink" title="三、运算器"></a>三、运算器</h2><h3 id="1-定点加减法运算"><a href="#1-定点加减法运算" class="headerlink" title="1.定点加减法运算"></a>1.定点加减法运算</h3><h5 id="①运算原理"><a href="#①运算原理" class="headerlink" title="①运算原理"></a>①运算原理</h5><ul><li><p>运算公式</p><ul><li>补码加法:$[x]_b+[y]_b=[x+y]_b\quad(mod\enspace M)$</li><li>补码减法:$[x-y]_b=[x]_b+[-y]_b=[x]_b-[y]_b\quad(mod\enspace M)$</li><li>对于定点小数,$M=2$;</li><li>对于定点整数,$M=2^{n+1}$,其中$n$为不包含符号位的位数。</li></ul></li><li><p>运算规则</p><ul><li>操作数采用<u>补码</u>表示,符号位参加运算;</li><li>运算结果为<u>补码</u>,符号位的进位位(模)直接丢弃。</li></ul></li><li><p>溢出判断</p><ul><li><p>单符号判断法:利用操作数和运算结果的符号位进行判断</p><ul><li><p>设$X_f$,$Y_f$为运算操作数的符号位,$S_f$为运算结果的符号位,$V$为溢出标志位,当$V=1$时表示发生溢出</p></li><li><p>加法溢出规则:“正正得负,负负得正”</p><p>$V=X_fY_f\overline{S_f}+\overline{X_f}\enspace \overline{Y_f}S_f$</p></li><li><p>减法溢出规则:“正负得负,负正得正”</p><p>$V=X_f\overline{Y_f}\enspace \overline{S_f}+\overline{X_f}Y_fS_f$</p></li></ul></li><li><p>进位位判断法:符号位进位和最高数据位进位进行异或操作</p><ul><li>设运算时最高有效数据位产生的进位信号为$C_d$,符号位产生的进位信号为$C_f$,溢出检测逻辑表达式为$V=C_f\oplus C_d$</li><li><strong>对于加减法均适用</strong></li></ul></li><li><p>双符号判断法</p><ul><li>根据变形补码中双符号位的定义,可以得到,当符号位为$01$或$10$时发生溢出;</li><li>溢出检测逻辑表达式公式为$V=S_{f_1}\oplus S_{f_2}$</li><li>将双符号运算结果的两个符号位进行异或操作</li></ul></li></ul></li></ul><h5 id="②一位全加器-FA-(-Full-enspace-Adder-,一个带进位的一位加法器)"><a href="#②一位全加器-FA-(-Full-enspace-Adder-,一个带进位的一位加法器)" class="headerlink" title="②一位全加器$FA$($Full\enspace Adder$,一个带进位的一位加法器)"></a>②一位全加器$FA$($Full\enspace Adder$,一个带进位的一位加法器)</h5><ul><li><p>计算原理</p><ul><li>$S_i=X_i\oplus Y_i\oplus C_i$;</li><li>$C_{i+1}=X_iY_i+(X_i+Y_i)C_i\quad \Delta2T$;</li><li>$C_{i+1}=X_iY_i+(X_i\oplus Y_i)C_i\quad \Delta5T$</li></ul></li><li><p>逻辑实现</p></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E4%B8%80%E4%BD%8D%E5%85%A8%E5%8A%A0%E5%99%A8.png!blogimg" class="" width="400" title="一位全加器"><h5 id="③多位串行加法器(-Ripple-enspace-Carry-enspace-Adder-,行波进位加法器)"><a href="#③多位串行加法器(-Ripple-enspace-Carry-enspace-Adder-,行波进位加法器)" class="headerlink" title="③多位串行加法器($Ripple\enspace Carry\enspace Adder$,行波进位加法器)"></a>③多位串行加法器($Ripple\enspace Carry\enspace Adder$,行波进位加法器)</h5><ul><li><p>对$n$位串行进位加法器进行简单的改造即可得到$n$位的加法电路;</p></li><li><p>无符号溢出为$C_n$,有符号溢出为$overflow=C_n\oplus C_{n-1}$;</p></li><li><p>计算原理</p><ul><li><p>$C_n\quad \Delta(2n+3)T$</p></li><li><p>$S_{n-1}\quad \Delta(2n+4)T$</p></li><li><p>$overflow\quad \Delta(2n+6)T$</p></li></ul></li><li><p>逻辑实现</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E5%A4%9A%E4%BD%8D%E4%B8%B2%E8%A1%8C%E5%8A%A0%E6%B3%95%E5%99%A8.png!blogimg" class="" width="400" title="多位串行加法器"></li><li><p>当采用多位串行加法器进行减法运算时,<strong>需要将减数的补码送入加法器</strong>。</p></li></ul><h5 id="④可控加减法电路(-Controlled-enspace-Adder-x2F-Subtractor-)"><a href="#④可控加减法电路(-Controlled-enspace-Adder-x2F-Subtractor-)" class="headerlink" title="④可控加减法电路($Controlled\enspace Adder/Subtractor$)"></a>④可控加减法电路($Controlled\enspace Adder/Subtractor$)</h5><ul><li><p>在$n$位串行加法器的基础上引入$sub$信号;</p></li><li><p>$sub$信号为$1$时表示进行减法,$sub$信号为$0$时表示进行加法;</p></li><li><p>计算原理</p><ul><li><p>数据位为$Y_i’=Y_i\oplus sub$</p></li><li><p>最低位的进位输入为$sub$</p></li></ul></li><li><p>逻辑实现</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E5%8F%AF%E6%8E%A7%E5%8A%A0%E5%87%8F%E6%B3%95%E7%94%B5%E8%B7%AF.png!blogimg" class="" width="400" title="可控加减法电路"></li></ul><h5 id="⑤先行进位加法器(-Carry-enspace-Look-Ahead-enspace-Adder-)"><a href="#⑤先行进位加法器(-Carry-enspace-Look-Ahead-enspace-Adder-)" class="headerlink" title="⑤先行进位加法器($Carry\enspace Look-Ahead\enspace Adder$)"></a>⑤先行进位加法器($Carry\enspace Look-Ahead\enspace Adder$)</h5><ul><li><p>计算原理</p><ul><li><p>设进位生成函数:$G_i=X_iY_i$,进位传递函数:$P_i=X_i\oplus Y_i$</p></li><li><p>由$S_i=X_i\oplus Y_i\oplus C_i$,$C_{i+1}=X_iY_i+(X_i\oplus Y_i)C_i$可得</p><p>$S_i=P_i\oplus C_i$,$C_{i+1}=G_i+P_iC_i$</p></li><li><p>进位信号仅与$G$,$P$,$C_0$有关:</p><p>$C_n=G_{n-1}+P_{n-1}G_{n-2}+P_{n-1}P_{n-2}G_{n-3}+\cdots+P_{n-1}P_{n-2}\cdots P_1P_0C_0$</p></li><li><p>成组进位生成函数:$G^*=G_{n-1}+P_{n-1}G_{n-2}+P_{n-1}P_{n-2}G_{n-3}+\cdots+P_{n-1}P_{n-2}\cdots G_0$</p></li><li><p>成组进位传递函数:$P^*=P_{n-1}P_{n-2}\cdots P_0$</p></li><li><p>$C_n=G^*+P^*C_0$与$C_1=G_0+P_0C_0$拥有相同的形式,即$4$位一组的进位信号可以采用相似的原理组成成组的先行进位,便于级联操作。</p></li></ul></li><li><p>逻辑实现($4$位先行进位电路,$CLA$)</p><ul><li>$4$位先行进位电路的总延迟为$2T$</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-4%E4%BD%8D%E5%85%88%E8%A1%8C%E8%BF%9B%E4%BD%8D%E7%94%B5%E8%B7%AF.png!blogimg" class="" width="400" title="4位先行进位电路"></li><li><p>逻辑实现(四位快速加法器)</p><ul><li>利用$CLA$实现的四位快速加法器总延迟为$8T$;</li><li>而串行加法器的时间延迟$(2n+4)T=12T$,相比之下性能提升$1.5$倍。</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-4%E4%BD%8D%E5%BF%AB%E9%80%9F%E5%8A%A0%E6%B3%95%E5%99%A8.png!blogimg" class="" width="400" title="4位快速加法器"></li><li><p>逻辑实现($16$位组内并行、组间串行加法器)</p><p>关键延迟为$14T$,相比串行的$(2n+4)T=36T$,性能提升$2.6$倍。</p></li><li><p>逻辑实现($16$位组内、组间并行加法器)</p><ul><li>将$G^*$和$P^*$送至可级联先行进位电路$(2T)$,实现组间并行;</li><li>延迟为$12T$,相比串行加法器的时间延迟$(2n+4)T=36T$,性能提升$3$倍。</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-16%E4%BD%8D%E5%BF%AB%E9%80%9F%E5%8A%A0%E6%B3%95%E5%99%A8.png!blogimg" class="" width="400" title="16位快速加法器"></li></ul><h3 id="2-定点乘法运算"><a href="#2-定点乘法运算" class="headerlink" title="2.定点乘法运算"></a>2.定点乘法运算</h3><h4 id="1-原码一位乘法"><a href="#1-原码一位乘法" class="headerlink" title="(1)原码一位乘法"></a>(1)原码一位乘法</h4><ul><li><p>符号位运算规则:<strong>符号位单独运算,乘积符号位等于乘数和被乘数符号的异或</strong>;</p></li><li><p>数值位运算规则:采用绝对值进行运算,设<u>数值位长度</u>为$n$,如下图所示:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E5%8E%9F%E7%A0%81%E4%B8%80%E4%BD%8D%E4%B9%98%E6%B3%95%E8%AE%A1%E7%AE%97%E8%BF%87%E7%A8%8B.png!blogimg" class="" width="400" title="原码一位乘法计算过程"><ul><li><p>可见,乘法可由加法实现,存在的问题:</p><ul><li><p>需要<strong>多输入的全加器</strong></p></li><li><p>需要长度为$2n$的积寄存器</p></li><li><p>对应乘数的不同位,部分积左移次数不同,且乘法过程中总移位次数多</p></li></ul></li></ul></li><li><p>运算改进方法:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E5%8E%9F%E7%A0%81%E4%B8%80%E4%BD%8D%E4%B9%98%E6%B3%95%E6%94%B9%E8%BF%9B%E7%9A%84%E8%AE%A1%E7%AE%97%E8%BF%87%E7%A8%8B.png!blogimg" class="" width="400" title="原码一位乘法改进的计算过程"><ul><li><p>采用基于一位全加器$FA$的循环累加$0$或者被乘数</p></li><li><p>从部分积和乘数寄存器取结果</p></li><li><p>每次进行累加时,右移部分积,同时右移乘数寄存器,将部分积移出位送入乘数寄存器高位,即将<u>{部分积,乘数寄存器}</u>组合后一起算数右移</p><blockquote><p>由于原码一位乘法中,<u><strong>符号位不参与运算</strong></u>,因此这里的算术右移操作是指将<u><strong>进位位</strong></u>作为算术右移后的最高位</p></blockquote></li></ul></li><li><p>改进后的运算公式:</p><ul><li><p>${P,y}={(P+y_n\vert x\vert),y}/2$</p></li><li><p>部分积$P$的初值为零,每次将部分积$P$累加上$y_n\vert \vert$后连同数据$y$一起同步算术右移得到新的部分积,一共要进行$n$次运算和移位操作,最终的$2n$位乘积存放在$P$和$y$两个寄存器中。</p><blockquote><p>逻辑左移:数据整体左移一位,最高位$D_{15}$被移出至$CF$,最低位$D_1$补$0$;</p><p>算术左移:数据整体左移一位,最高位$D_{15}$被移动,最低位$D_1$补$0$;</p><p>逻辑右移:数据整体右移一位,最高位$D_{15}$补$0$,最低位$D_1$被移出;</p><p>算术右移:数据整体右移一位,最高位$D_{15}$填补符号位,最低位$D_1$被移出。</p></blockquote></li></ul></li><li><p>逻辑表示:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E5%8E%9F%E7%A0%81%E4%B8%80%E4%BD%8D%E4%B9%98%E6%B3%95%E9%80%BB%E8%BE%91%E5%9B%BE.png!blogimg" class="" width="400" title="原码一位乘法逻辑图"></li></ul><h4 id="2-补码一位乘法"><a href="#2-补码一位乘法" class="headerlink" title="(2)补码一位乘法"></a>(2)补码一位乘法</h4><ul><li><p>运算规则:</p><ul><li><p>补码一位乘法中<strong>符号位参加运算</strong>,乘数取单符号位;</p></li><li><p><u>具体运算公式</u>:${P,y}={(P+(y_{n+1}-y_n)[x]_b,y}/2$;</p></li><li><p>在数据末位增加一位附加位$y_{n+1}=0$,部分积$P$初值为$0$</p></li><li><p>当$Y_nY_{n+1}=00$或$11$时,部分积加$0$</p></li><li><p>当$Y_nY_{n+1}=01$时,部分积加$[x]_b$</p></li><li><p>当$Y_nY_{n+1}=10$时,部分积加$[-x]_b$</p></li><li><p>每次部分积计算完毕后连同乘数$y$一起同步算术右移一位</p><blockquote><p>由于补码一位乘法中,<u><strong>符号位参与运算</strong></u>,因此这里的算术右移操作是指将<u><strong>符号位</strong></u>作为算术右移后的最高位</p></blockquote></li><li><p>由于符号位参与了运算,累加运算需要进行$n+1$次,但移位次数只需要进行$n$次</p></li></ul></li><li><p>逻辑表示:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E8%A1%A5%E7%A0%81%E4%B8%80%E4%BD%8D%E4%B9%98%E6%B3%95%E9%80%BB%E8%BE%91%E5%9B%BE.png!blogimg" class="" width="400" title="补码一位乘法逻辑图"></li></ul><h4 id="3-阵列乘法器"><a href="#3-阵列乘法器" class="headerlink" title="(3)阵列乘法器"></a>(3)阵列乘法器</h4><h5 id="①横向进位原码阵列乘法电路"><a href="#①横向进位原码阵列乘法电路" class="headerlink" title="①横向进位原码阵列乘法电路"></a>①横向进位原码阵列乘法电路</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E6%A8%AA%E5%90%91%E8%BF%9B%E4%BD%8D%E5%8E%9F%E7%A0%81%E9%98%B5%E5%88%97%E4%B9%98%E6%B3%95%E7%94%B5%E8%B7%AF.png!blogimg" class="" width="400" title="横向进位原码阵列乘法电路"><p>$n$位的阵列乘法器需要$n(n-1)$个全加器,时间延迟为$[n+2(n-2)]*3T+T=(3n-4)*3T+T.$</p><h5 id="②斜向进位原码阵列乘法电路"><a href="#②斜向进位原码阵列乘法电路" class="headerlink" title="②斜向进位原码阵列乘法电路"></a>②斜向进位原码阵列乘法电路</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E6%96%9C%E5%90%91%E8%BF%9B%E4%BD%8D%E5%8E%9F%E7%A0%81%E9%98%B5%E5%88%97%E4%B9%98%E6%B3%95%E7%94%B5%E8%B7%AF.png!blogimg" class="" width="400" title="斜向进位原码阵列乘法电路"><p>$n$位的阵列乘法器需要$n(n-1)$个全加器,时间延迟为$2*(n-1)*3T+T=(2n-2)*3T+T.$</p><h5 id="③原码阵列乘法器"><a href="#③原码阵列乘法器" class="headerlink" title="③原码阵列乘法器"></a>③原码阵列乘法器</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E5%8E%9F%E7%A0%81%E9%98%B5%E5%88%97%E4%B9%98%E6%B3%95%E5%99%A8.png!blogimg" class="" width="400" title="原码阵列乘法器"><h5 id="④补码阵列乘法器"><a href="#④补码阵列乘法器" class="headerlink" title="④补码阵列乘法器"></a>④补码阵列乘法器</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E8%A1%A5%E7%A0%81%E9%98%B5%E5%88%97%E4%B9%98%E6%B3%95%E5%99%A8.png!blogimg" class="" width="400" title="补码阵列乘法器"><h5 id="⑤阵列乘法器流水线"><a href="#⑤阵列乘法器流水线" class="headerlink" title="⑤阵列乘法器流水线"></a>⑤阵列乘法器流水线</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E9%98%B5%E5%88%97%E4%B9%98%E6%B3%95%E5%99%A8%E6%B5%81%E6%B0%B4%E7%BA%BF.png!blogimg" class="" width="400" title="阵列乘法器流水线"><h3 id="3-浮点运算"><a href="#3-浮点运算" class="headerlink" title="3.浮点运算"></a>3.浮点运算</h3><ul><li><p>阶码和尾数采用补码表示的浮点数加减运算</p><ul><li><p>运算概述</p><ul><li><p>设有两个浮点数$X=2^m\times M_x$,$Y=2^n\times M_y$</p></li><li><p>当$m=n$时,尾数部分直接运算即可得到浮点形式的运算结果;</p></li><li><p>当$m\ne n$时,需要使二者阶码相等后再行尾数部分的运算,称为<u><strong>对阶</strong></u>;</p></li><li><p>尾数的计算结果可能不满足规格化,需要进行规格化处理。</p></li></ul></li><li><p>运算过程</p><ul><li><p>对阶(小阶向大阶看齐)</p><ul><li>求阶差:对阶码进行减法运算,得到阶码的差值</li><li>阶码的调整与尾数的移位:将阶码较小的浮点数的尾数<strong>右移$m-n$位</strong></li></ul></li><li><p>尾数运算(进行定点运算)</p><ul><li>按照定点数的补码加减法运算执行尾数加减操作</li></ul></li><li><p>规格化运算结果,需要规格化时进行左规或右规操作</p><ul><li>为了处理方便,让尾数的符号位扩展成<strong>双符号位</strong></li><li>当运算结果为$11.0\cdots\cdots$或者$00.1\cdots\cdots$的形式时为<u><strong>规格化</strong></u>数</li><li>非规格化处理<ul><li>当运算结果为$10.\cdots\cdots$或者$01.\cdots\cdots$的形式时发生上溢,将尾数<u><strong>右移一位</strong></u>,并将结果的<strong>阶码加1</strong>;</li><li>当运算结果为$11.1\cdots\cdots$或者$00.0\cdots\cdots$的形式时,需要左规格化,尾数连同符号位一起左移,直到出现$11.0\cdots\cdots$或者$00.1\cdots\cdots$的形式时结束,<strong>左移多少位阶码就减多少</strong>。</li></ul></li></ul></li><li><p>舍入处理</p><ul><li><u><strong>末位恒置$1$法</strong></u>:只要因为移位丢失的位中有一位为$1$,便在运算结果最低位加$1$;</li><li><u><strong>$0$舍$1$入法</strong></u>:当丢失位数的最高位为$1$时在运算结果最低位加$1$;</li><li>舍入后可能还需要进行<strong>二次规格化</strong></li></ul></li><li><p>溢出判断</p><ul><li><strong>阶码溢出时浮点数才会发生溢出</strong></li></ul></li></ul></li></ul></li><li><p>$IEEE754$浮点数加减运算</p><ul><li><p>对阶和规格化过程中,阶码运算采用<u><strong>移码加减法</strong></u>运算规则;</p></li><li><p>尾数的运算采用原码运算规则,且隐藏位要参与运算;</p></li><li><p>规格化过程</p><ul><li>若尾数形式为$1.\cdots\cdots$,则为规格化尾数;</li><li>若尾数形式为$1X.\cdots\cdots$,则向右规格化一次,阶码加$1$;</li><li>若尾数形式为$0.\cdots\cdots$,则向左规格化直至变为$1.\cdots\cdots$,<strong>左移多少位阶码就减多少</strong></li></ul></li><li><p>溢出判断</p><ul><li>向右规格化使阶码为全$1$时,发生规格化上溢;</li><li>向左规格化使阶码为全$0$时,发生规格化下溢;</li></ul></li></ul></li></ul><h3 id="4-运算器"><a href="#4-运算器" class="headerlink" title="4.运算器"></a>4.运算器</h3><ul><li><p>定点运算器</p><ul><li>算术逻辑运算单元$ALU$<ul><li>$n$位$ALU$包括两个$n$位的输入操作数$a$、$b$,一位进位输入$C_{in}$,$AluOp$为运算功能选择操作码,用于选择$ALU$内部的运算电路;</li><li>在$ALU$内部,所有逻辑、算术运算电路并发运行,多个运算结果分别送入多路选择器输入端,由$AluOp$选择其中一路结果输出;</li><li>输出除了$result$外,还包括若干状态标志位:$CF$、$ZF$、$OF$、$SF$。</li></ul></li><li>通用寄存器组<ul><li>作用:暂存参加运算的数据、运算的中间结果或最后结果。</li></ul></li><li>输人、输出选择电路<ul><li>作用:对若干个数据的输入、输出进行选择或控制。</li></ul></li></ul></li><li><p>运算器结构</p><ul><li>单总线结构:$2$个缓冲器,$3$个时钟周期完成运算</li><li>双总线结构:$1$个缓冲器,$2$个时钟周期完成运算</li><li>三总线结构:$0$个缓冲器,$1$个时钟周期完成运算</li></ul></li><li><p>浮点运算器</p><ul><li><p>浮点流水线,将浮点运算的步骤进行细分,优化密集型浮点运算性能</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-2/Chp3-%E6%B5%AE%E7%82%B9%E8%BF%90%E7%AE%97%E5%99%A8.png!blogimg" class="" width="400" title="浮点运算器"></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机组成原理 </tag>
</tags>
</entry>
<entry>
<title>计算机组成原理笔记(三)</title>
<link href="/posts/2022/06/12/notes/Principles-of-Computer-Composition-3/"/>
<url>/posts/2022/06/12/notes/Principles-of-Computer-Composition-3/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="四、存储系统"><a href="#四、存储系统" class="headerlink" title="四、存储系统"></a>四、存储系统</h2><h3 id="1-存储器概述"><a href="#1-存储器概述" class="headerlink" title="1.存储器概述"></a>1.存储器概述</h3><ul><li>存储器分类</li></ul><table><thead><tr><th align="center">划分依据</th><th align="center">具体分类</th></tr></thead><tbody><tr><td align="center">存取方式</td><td align="center">随机存储器(半导体存储器)、顺序存储器(磁带)、直接存储器(磁盘)</td></tr><tr><td align="center">读写功能</td><td align="center">只读存储器($ROM$)、读写存储器($RAM$)</td></tr><tr><td align="center">作用</td><td align="center">控制存储器、高速缓冲存储器$Cache$、主存储器、辅助存储器(磁盘)</td></tr></tbody></table><ul><li><p>存储系统技术指标</p><ul><li><p><strong>存储时间</strong>:从<u>接受到读写命令</u>到<u>从存储器中读出或写入信息</u>所经历的时间</p></li><li><p><strong>存储周期</strong>:连续两次访问存储器所需要的<u>最小</u>时间间隔 (存储时间+恢复时间)</p></li><li><p><strong>存储器带宽</strong>:单位时间内存储器存取的信息量,单位为$Byte/s$</p></li></ul></li><li><p>基本存储体系</p><ul><li>基本存储体系运作流程<ol><li>输入设备将程序与数据写入主存;</li><li>$CPU$取指令;</li><li>$CPU$执行指令期间读数据;</li><li>$CPU$写回运算结果;</li><li>输出设备输出结果。</li></ol></li><li>基本存储体系的问题<ul><li><strong>主存速度慢</strong>:主存增速与$CPU$增速不同步,指令执行期间多次访问主存</li><li><strong>主存容量不足</strong>:存在制约主存容量的技术因素(由$CPU$、主板等相关技术指标确定),应用对主存的需求不断扩大,存在价格约束</li></ul></li></ul></li><li><p>存储体系的层次化结构</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%AD%98%E5%82%A8%E5%99%A8%E7%9A%84%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="存储器的层次结构"><ul><li><p>使用高速缓冲存储器($Cache$):解决$CPU$与主存速度不匹配的矛盾</p><ul><li><p>$L1\enspace Cache$集成在$CPU$中,分数据$Cache(D-Cache)$和指令$Cache(I-Cache)$</p></li><li><p>早期$L2\enspace Cache$在主板上或与$CPU$集成在同一电路板上。随着工艺的提高,$L2\enspace Cache$被集成在$CPU$内核中,不分$D-Cache$和$I-Cache$</p></li></ul></li><li><p>使用辅存(磁盘、磁带、网络存储):解决主存容量和价格限制</p></li><li><p><strong>$CPU$访问到的存储系统具有$Cache$的速度,辅存的容量和价格</strong></p></li></ul></li><li><p>存储单元、存储地址与边界对齐</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%AD%98%E5%82%A8%E5%8D%95%E5%85%83%E4%B8%8E%E5%AD%98%E5%82%A8%E5%9C%B0%E5%9D%80.png!blogimg" class="" width="400" title="存储单元与存储地址"><ul><li><p>主存由半导体$MOS$存储器组成,给出一个地址,经过一个存储周期,可以访问一个存储单元;</p></li><li><p><u>存储单元比特位数</u>和机器字长有关,$32$位计算机中主存单元是$32$位,$64$位计算机主存单元是$64$位;</p></li><li><p>以$32$位计算机为例,$32$位字长的主存单元可以按照不同大小访问,既可以按照<u>字节</u>访问,也可以按照<u>半字</u>$16$位或者<u>字存储单元</u>$32$位进行访问;</p></li><li><p><strong>不同访问模式的访问时间都是一个存储周期</strong>,访问地址分别对应字节地址、半字地址和字地址;</p></li><li><p>例如对于以下指令:</p><p>$mov\enspace ah, [8]$ #按字节访存 $ah=0x12$<br>$mov\enspace ax, [8]$ #按半字访存 $ax=0x3412$<br>$mov\enspace eax,[8]$ #按字访存 $eax=0x78563412$<br>$mov\enspace eax,[9]$ #<strong>未对齐,产生异常</strong></p></li><li><p>强制边界对齐</p><ul><li>计算机中<u>只有字节地址</u>;</li><li>按半字访问时<u>屏蔽字节地址最低位</u>,也即半字地址必须按$2$对齐;</li><li>按字访问是<u>屏蔽字节地址最低$2$位</u>,也即字地址必须按$4$对齐;</li><li>如果不对齐,将导致访问数据不在一个存储单元中,需要两个以上的存储周期才能得到,计算机中不允许出现这种情况,因为这样的指令执行会产生数据未对齐异常。</li></ul></li></ul></li></ul><h3 id="2-主存的组织及与-CPU-的连接"><a href="#2-主存的组织及与-CPU-的连接" class="headerlink" title="2.主存的组织及与$CPU$的连接"></a>2.主存的组织及与$CPU$的连接</h3><ul><li><p>字长扩展(数据总线扩展)</p><ul><li><p>各芯片并行工作,数据由多个芯片的输出组合得到</p></li><li><p>设存储系统位宽$N$位,若使用$k$位芯片,$k<N$,需$(N/k)$个芯片</p></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%AD%97%E9%95%BF%E6%89%A9%E5%B1%95.png!blogimg" class="" width="400" title="字长扩展"></li><li><p>字数扩展(地址总线扩展)</p><ul><li><p>同一时刻仅一个芯片工作,数据由一个芯片的输出得到</p></li><li><p>设存储系统容量为$M$,若使用容量为$l$的芯片,$l<M$,需$(M/l)$个芯片</p></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%AD%97%E6%95%B0%E6%89%A9%E5%B1%95.png!blogimg" class="" width="400" title="字数扩展"></li><li><p>综合扩展</p><p>存储系统$M\times N$位,若使用$l\times k$位的芯片,$l<M,k<N$,需$(M/l)\times (N/k)$个芯片</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E7%BB%BC%E5%90%88%E6%89%A9%E5%B1%95.png!blogimg" class="" width="400" title="综合扩展"></li></ul><h3 id="3-并行主存系统"><a href="#3-并行主存系统" class="headerlink" title="3.并行主存系统"></a>3.并行主存系统</h3><ul><li><p>双端口存储器</p><ul><li>具有两组相互独立的读写控制线路</li><li>两组读写控制线路可以并行操作</li><li>端口地址不相同,无冲突,并行存取</li><li>端口地址相同,读写冲突,无法并行存取</li></ul></li><li><p>多体交叉存储器</p><ul><li><p>高位多体交叉</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E9%AB%98%E4%BD%8D%E5%A4%9A%E4%BD%93%E4%BA%A4%E5%8F%89.png!blogimg" class="" width="400" title="高位多体交叉"><ul><li>一个地址寄存器</li><li>高位片选,多模块串行</li><li>扩容方便</li></ul></li><li><p>低位多体交叉</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E4%BD%8E%E4%BD%8D%E5%A4%9A%E4%BD%93%E4%BA%A4%E5%8F%89.png!blogimg" class="" width="400" title="低位多体交叉"><ul><li>每个存储体均需地址寄存器</li><li>低位片选,模块并行工作</li><li>$CPU$比存储器要快,能同时取出多条指令或者数据</li><li>扩容方便</li></ul></li><li><p>低位多体交叉下的流水线访问</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E4%BD%8E%E4%BD%8D%E5%A4%9A%E4%BD%93%E4%BA%A4%E5%8F%89%E4%B8%8B%E7%9A%84%E6%B5%81%E6%B0%B4%E7%BA%BF%E8%AE%BF%E9%97%AE.png!blogimg" class="" width="400" title="低位多体交叉下的流水线访问"><ul><li><p>设模块存取周期为$T$,总线传输周期为$t$,存储器交叉模块数为$m$;</p></li><li><p>流水线方式存取的条件:$T=m\times t$;</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E4%BD%8E%E4%BD%8D%E5%A4%9A%E4%BD%93%E4%BA%A4%E5%8F%89%E4%B8%8B%E7%9A%84%E6%B5%81%E6%B0%B4%E7%BA%BF%E8%AE%BF%E9%97%AE%E6%97%B6%E9%97%B4%E8%A1%A8.png!blogimg" class="" width="400" title="低位多体交叉下的流水线访问时间表"></li><li><p>即每个模块启动后经过$t$时间的延时,就可以启动下一个模块;</p></li><li><p>连续并行读$m$个字的时间:$t_1=T+(m-1)t$</p></li><li><p>顺序读$m$个字的时间:$t_2=mT$</p></li></ul></li></ul></li></ul><h3 id="4-高速缓冲存储器"><a href="#4-高速缓冲存储器" class="headerlink" title="4.高速缓冲存储器"></a>4.高速缓冲存储器</h3><h4 id="1-缓冲工作原理"><a href="#1-缓冲工作原理" class="headerlink" title="(1)缓冲工作原理"></a>(1)缓冲工作原理</h4><ul><li><p>早期$U$盘</p><ul><li><p>早期计算机中,如果要拔出$USB$设备,计算机会提示进行安全拔出</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E4%B8%8D%E5%AE%89%E5%85%A8%E7%9A%84%E8%AE%BE%E5%A4%87%E6%8B%94%E5%87%BA.png!blogimg" class="" width="400" title="不安全的设备拔出"></li><li><p>早期$U$盘<u>写速度慢</u>,计算机会将部分<strong>内存空间</strong>为$U$盘作<strong>写缓冲</strong>;</p></li><li><p><strong>当数据写入内存时,计算机即报告传输完成</strong>,这样可以改善用户等待体验;</p></li><li><p>但同时会导致数据不一致性,产生<strong>脏数据</strong>——在内存中还未写入$U$盘的数据;</p></li><li><p>系统将<strong>定时</strong>或<strong>被迫</strong>将脏数据迁移到U盘,不安全的拔盘可能丢失脏数据。</p></li></ul></li><li><p>缓冲机制</p><ul><li>缓冲的前提<ul><li>有较大性能差异(如$CPU$和主存)</li><li>缓冲空间足够</li></ul></li><li>快存为慢存作写缓冲,<strong>优化写性能</strong></li></ul></li></ul><h4 id="2-程序局部性原理"><a href="#2-程序局部性原理" class="headerlink" title="(2)程序局部性原理"></a>(2)程序局部性原理</h4><ul><li><p>程序局部性:程序仅需访问内存很小一部分空间</p><ul><li><p>空间局部性: 某内存区域刚被访问,很快其相邻区域有可能被访问</p><ul><li><p>数据——连续数组元素访问</p></li><li><p>数据——结构体、数据库记录访问</p></li><li><p>指令——顺序访问的指令</p></li></ul></li><li><p>时间局部性: 某内存区域刚被访问,很快该区域可能会被重复访问</p><ul><li>数据——局部变量,计数器,指针等被重复使用</li><li>指令——重复使用的循环体中的指令</li><li>指令——子函数的调用</li></ul></li></ul></li><li><p>读性能的优化</p><ul><li>利用<strong>数据访问的<u>空间</u>局部性</strong>进行读优化</li><li>将<strong>热数据或即将访问数据的副本</strong>调度到存储系统上层</li><li>仅访问上层快存即可获得数据</li></ul></li><li><p>$cache$的基本思想</p><ul><li><p>在处理器附近增加一个<strong>隐藏的小容量快速存储器</strong></p><ul><li>采用<u>硬件</u>实现,程序员无法操纵</li><li>高命中率优化读性能<ul><li>预读处理——空间局部性(预读相邻的数据)</li><li>淘汰算法——时间局部性(淘汰掉最近最不常访问的数据)</li><li>将<u>热数据</u>副本存放在$cache$中</li></ul></li></ul></li><li><p>读操作流程</p><ul><li><p>命中($HIT$)</p><p>$CPU$发出读请求,要访问的数据可以在$cache$中找到,直接返回数据</p></li><li><p>缺失($MISS$)</p><p>$CPU$发出读请求,要访问的数据在$cache$中无法找到,需要在主存中找到后拷贝到$cache$中,再返回数据,这将导致访问速度急剧下降</p></li></ul></li><li><p>写操作流程</p><ul><li><p>写穿策略($WriteThrough$)</p><p>CPU发出写请求,将要写入的数据写入<strong>cache和主存</strong>中,写入完成后响应,无脏数据,无丢失数据的风险,写速度慢</p></li><li><p>写回策略($WriteBack$)</p><p>CPU发出写请求,将要写入的数据写入$cache$中即返回写响应,存在脏数据,有丢失数据的风险,突发写速度快;持续写时,$cache$很快就会存满数据,需要先将$cache$中数据移到主存,然后再写入$cache$</p></li></ul></li></ul></li></ul><h4 id="3-cache基本概念"><a href="#3-cache基本概念" class="headerlink" title="(3)cache基本概念"></a>(3)cache基本概念</h4><ul><li><p>命中$(hit)$:$CPU$访问数据在$cache$中(上层存储器)</p></li><li><p>缺失$(miss)$:$CPU$访问数据不在$cache$中</p></li><li><p>块$(block)$:$cache$与主存交换最小单位</p><ul><li>块越小,时间局部性越好,过小会导致频繁交换;</li><li>块越大,空间局部性越好,过大导致$cache$很快会被装满,对热数据的调度差</li></ul></li><li><p>行/槽$(Line/Slot)$:包括有效位、查找标记、脏标志位、置换标志、数据块副本的容器</p></li><li><p>命中率$(hit\enspace rate)$:主存访问中$cache$命中比例</p></li><li><p>缺失率$(miss\enspace rate)$:1-命中率</p></li><li><p>命中访问时间$(hit\enspace time)$:包括数据查找时间、$cache$访问时间、总线传输时间</p></li><li><p>缺失损失$(miss\enspace penalty)$:主存块调入$cache$,数据传输到$CPU$的时间,远大于命中时间</p></li></ul><h4 id="4-cache读流程"><a href="#4-cache读流程" class="headerlink" title="(4)cache读流程"></a>(4)cache读流程</h4><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-cache%E8%AF%BB%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="cache读流程"><ul><li>$CPU$给出主存地址</li><li>以<u>主存块地址</u>为关键字在$cache$中查找,<strong>需要维护查找表</strong><ul><li>如相符表示副本在$cache$中,命中,访问$cache$</li><li>否则数据缺失,访问主存<ul><li>将数据所在块的副本调入$cache$</li><li><strong>更新查找表</strong>,记录当前数据块地址</li><li>载入副本过程<strong>可能引起替换</strong></li><li>$cache$缺失时,<strong>CPU需要等待数据调入</strong></li></ul></li></ul></li></ul><h4 id="5-cache写流程"><a href="#5-cache写流程" class="headerlink" title="(5)cache写流程"></a>(5)cache写流程</h4><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-cache%E5%86%99%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="cache写流程"><ul><li>$CPU$给出主存地址</li><li>以<u>主存块地址</u>为关键字进行查找<ul><li>相符:表示命中,数据副本在$cache$中</li><li>缺失:根据写分配策略决定是否将该主存地址对应数据块调入<ul><li>写分配:为主存块分配$cache$行</li><li>写不分配:静默地将数据写入主存,而不分配$cache$行</li></ul></li></ul></li><li>写入数据到$cache$,并根据写策略决定是否写入主存</li></ul><h4 id="6-cache查找机制"><a href="#6-cache查找机制" class="headerlink" title="(6)cache查找机制"></a>(6)cache查找机制</h4><ul><li>解决<strong>判断数据是否在$cache$中</strong>的问题,查找时间应快速且一致,不随着数据增长而增长</li><li>相联存储器:按内容进行访问的存储器</li></ul><table><thead><tr><th align="center">主存块号</th><th align="center">$cache$块号</th></tr></thead><tbody><tr><td align="center">001</td><td align="center">1</td></tr><tr><td align="center">021</td><td align="center">2</td></tr><tr><td align="center">023</td><td align="center">6</td></tr><tr><td align="center">$\dots$</td><td align="center">$\dots$</td></tr></tbody></table><ul><li><p>读逻辑实现</p><ul><li>查找表中有内容的$key-value$对的$valid$位置为$1$,否则$valid$位为$0$;</li></ul></li><li><p>采用多个比较器进行<strong>并发比较</strong>,每次比较时$L_0-L_7$至多有一个为$1$;</p><ul><li>根据$valid$位和比较结果使用三态门输出$key$对应的$value.$</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E7%9B%B8%E8%81%94%E5%AD%98%E5%82%A8%E5%99%A8%E8%AF%BB%E9%80%BB%E8%BE%91%E5%AE%9E%E7%8E%B0.png!blogimg" class="" width="400" title="相联存储器读逻辑实现"></li><li><p>相联存储器的特点</p><ul><li><p>按内容进行访问$(Key,Value)$</p><ul><li>以关键字进行<u>全局并发</u>比较</li><li>硬件成本较高(比较器多),通常用于存放查找表或全相联$cache$</li></ul></li><li><p>存储容量=查找表容量=表项数$\times $表项大小</p><ul><li>$cache$中用于存放块表,虚拟存储器中用于存放段表、页表</li></ul></li></ul></li></ul><table><thead><tr><th align="center">模式</th><th align="center">有效位</th><th align="center">$Key$</th><th align="center">$Value$</th></tr></thead><tbody><tr><td align="center">$cache$</td><td align="center">有效位</td><td align="center">主存块地址</td><td align="center">$cache$块地址</td></tr><tr><td align="center">虚拟存储器</td><td align="center">有效位</td><td align="center">$VPN$</td><td align="center">$PPN$</td></tr></tbody></table><ul><li><p>$CPU\enspace cache$的基本组织方式</p><ul><li>$CPU\enspace cache$由较快的$SRAM$构成</li><li>$cache$与主存均分为固定大小的数据块,以<u><strong>块</strong></u>为单位交换数据</li><li>相联存储器存放查找表/$cache$<ul><li>表项:(有效位,调入$cache$的主存块地址,$cache$块地址/$block\enspace data$)</li><li>容量 = $cache$块数 * 表项大小</li></ul></li><li>块地址与块内地址<ul><li>由于主存被分为若干主存块,每个主存块包含多个存储单元,因此主存地址可以分为<u>块地址</u>和<u>块内偏移</u>;</li><li>查找表表项内容为(valid,主存块地址,cache块地址)</li><li>查找表表项数目$=cache$块数目,总容量$=(1+11+8)*2^8$</li></ul></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%9D%97%E5%9C%B0%E5%9D%80%E4%B8%8E%E5%9D%97%E5%86%85%E5%9C%B0%E5%9D%80.png!blogimg" class="" width="400" title="块地址与块内地址"></li></ul><h4 id="7-cache映射机制"><a href="#7-cache映射机制" class="headerlink" title="(7)cache映射机制"></a>(7)cache映射机制</h4><ul><li>解决<strong>如何将主存数据放置在$cache$行中</strong>的问题,有规律的映射有助于查找</li><li>利用某种方法或规则将主存块定位到$cache$称为<strong>地址映射</strong><ul><li>全相联$(fully-associated)$</li><li>直接相联$(direct\enspace mapped)$</li><li>组相联$(set-associated)$</li></ul></li></ul><h5 id="①全相联"><a href="#①全相联" class="headerlink" title="①全相联"></a>①全相联</h5><ul><li><p>主存块可以放置在任意$cache$行;</p></li><li><p>主存地址为:<u>(主存块地址,块内偏移)</u></p></li><li><p>$cache$行内容为:<u>(有效位,主存块地址,数据块副本)</u></p></li><li><p>实现时可以直接将数据块副本存放在$cache$行中,而不需要$cache$块地址;</p></li><li><p>关系图</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%85%A8%E7%9B%B8%E8%81%94%E6%98%A0%E5%B0%84%E5%85%B3%E7%B3%BB%E5%9B%BE.png!blogimg" class="" width="400" title="全相联映射关系图"></li><li><p>逻辑实现</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%85%A8%E7%9B%B8%E8%81%94%E6%98%A0%E5%B0%84%E9%80%BB%E8%BE%91%E5%AE%9E%E7%8E%B0.png!blogimg" class="" width="400" title="全相联映射逻辑实现"></li><li><p>相联存储器容量</p><ul><li>查找表和缓存副本一体<u>(CPU片内缓存)</u><ul><li>存放$cache$行</li><li>有效位、主存块地址、<strong>数据块副本</strong>、标志位$(Dirty\enspace bit)$、置换标记</li><li>存储容量$=cache$行大小$\times$行数</li></ul></li><li>查找表和缓存副本分离<u>(片内查找表,片外缓存)</u><ul><li>查找表在$CPU$内部,存放查找信息,而缓存在主存上</li><li><strong>先将主存块地址翻译为$cache$块地址,再通过$cache$块地址访问片外缓存</strong></li><li>有效位、主存块地址、$cache$块地址、标志位$(Dirty\enspace bit)$、置换标记</li><li>存储容量$=$查找表表项大小$\times$行数</li></ul></li></ul></li><li><p>应用场合</p><ul><li>块映射灵活,一对多映射</li><li>块冲突概率低,$cache$装满后才会出现块冲突,$cache$利用率高</li><li>命中率高</li><li>淘汰算法复杂</li></ul></li></ul><h5 id="②直接相联"><a href="#②直接相联" class="headerlink" title="②直接相联"></a>②直接相联</h5><ul><li><p>将主存块分为若干个区,每个区内的主存块只能放在区内索引相同的$cache$行内;</p></li><li><p>主存地址为:<u>(区地址,行索引,块内偏移)</u></p></li><li><p>$cache$行内容为:<u>(有效位,主存块地址,数据块副本)</u></p></li><li><p>关系图</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E7%9B%B4%E6%8E%A5%E7%9B%B8%E8%81%94%E6%98%A0%E5%B0%84%E5%85%B3%E7%B3%BB%E5%9B%BE.png!blogimg" class="" width="400" title="直接相联映射关系图"></li><li><p>逻辑实现</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E7%9B%B4%E6%8E%A5%E7%9B%B8%E8%81%94%E6%98%A0%E5%B0%84%E9%80%BB%E8%BE%91%E5%AE%9E%E7%8E%B0.png!blogimg" class="" width="400" title="直接相联映射逻辑实现"></li><li><p>$cache$容量</p><ul><li>使用一个比较器,比较标记——<u>区地址</u></li><li>cache容量=行大小$\times$行数=(valid+脏数据标志+比较标记+数据块)$\times$行数</li></ul></li><li><p>应用场合</p><ul><li>映射速度快(但是<u>存在译码逻辑</u>),一对一映射,无须查表<ul><li>利用索引字段直接对比相应标记位即可</li><li>查找表可以和副本一起存放,无需相联存储器</li></ul></li><li>容易冲突,$cache$利用率低</li><li>命中率低,适合大容量$cache$</li><li>无需替换算法,按行索引替换</li></ul></li></ul><h5 id="③组相联"><a href="#③组相联" class="headerlink" title="③组相联"></a>③组相联</h5><ul><li><p><strong>组内全相联,组间直接相联</strong>;</p></li><li><p>将$cache$分为若干个组,每组有若干个$cache$行,在进行映射时,第$i$个主存块只能放在第$i$组,组内可以任意放置;</p></li><li><p>主存地址为:<u>(标记tag,组索引,块内偏移)</u></p></li><li><p>$cache$行内容为:<u>(有效位,标记tag,数据块副本)</u></p></li><li><p>关系图</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E7%BB%84%E7%9B%B8%E8%81%94%E6%98%A0%E5%B0%84%E5%85%B3%E7%B3%BB%E5%9B%BE.png!blogimg" class="" width="400" title="组相联映射关系图"></li><li><p>逻辑实现</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E7%BB%84%E7%9B%B8%E8%81%94%E6%98%A0%E5%B0%84%E9%80%BB%E8%BE%91%E5%AE%9E%E7%8E%B0.png!blogimg" class="" width="400" title="组相联映射逻辑实现"></li><li><p>存储容量</p><ul><li>多个相联存储器<u>共享</u>一个多路比较器(一组内的行数)<ul><li><strong>多路比较器复杂度比简单的全相联模式低</strong></li></ul></li><li>查找表表项内容:<u>(valid,dirty,查找标记,置换标记位)</u></li><li>相联存储器总容量:cache行数$\times$(1+1+查找标记宽度+置换标记位)</li></ul></li></ul><h5 id="④不同映射机制对比"><a href="#④不同映射机制对比" class="headerlink" title="④不同映射机制对比"></a>④不同映射机制对比</h5><ul><li><p>不同映射机制的主存地址划分</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E4%B8%8D%E5%90%8C%E6%98%A0%E5%B0%84%E6%96%B9%E5%BC%8F%E4%B8%BB%E5%AD%98%E5%9C%B0%E5%9D%80%E5%88%92%E5%88%86.png!blogimg" class="" width="400" title="不同映射方式主存地址划分"></li><li><p>不同映射机制的应用场合</p><ul><li>小容量$cache$可采用<u>全相联</u>映射或<u>组相联</u>映射<ul><li>$Pentium\enspace CPU:L_1\enspace L_2\enspace cache$</li></ul></li><li>大容量$cache$可采用<u>直接映射</u>方式<ul><li>查找速度快,命中率相对低,但$cache$容量大可提高命中率块,设备缓存</li></ul></li></ul></li></ul><h4 id="8-cache替换策略"><a href="#8-cache替换策略" class="headerlink" title="(8)cache替换策略"></a>(8)cache替换策略</h4><ul><li>解决<strong>cache满或者冲突时如何进行替换</strong>的问题,需要考虑时间局部性</li><li>先进先出法——$FIFO$</li><li>最近最不经常使用方法——$LFU$</li><li>近期最少使用法——$LRU$</li><li>随机替换法</li></ul><h4 id="9-cache写入策略"><a href="#9-cache写入策略" class="headerlink" title="(9)cache写入策略"></a>(9)cache写入策略</h4><ul><li>解决<strong>如何保证cache与memory的一致性</strong>的问题</li><li>写回法($cache$中<u>需要</u><strong>维护脏数据标志位</strong>)</li><li>写穿法($cache$中<u>不需要</u><strong>维护脏数据标志位</strong>)</li><li>写分配</li><li>写不分配</li></ul><h4 id="10-总结"><a href="#10-总结" class="headerlink" title="(10)总结"></a>(10)总结</h4><h5 id="①cache对存储系统性能的影响"><a href="#①cache对存储系统性能的影响" class="headerlink" title="①cache对存储系统性能的影响"></a>①cache对存储系统性能的影响</h5><ul><li>读优化<ul><li>时间局部性:将<u>刚访问的数据</u>调度到$cache$中、利用<u>淘汰算法</u>将不经常使用的数据淘汰</li><li>空间局部性:大块<u>预读</u>,相邻的数据被调度到$cache$中</li></ul></li><li>写优化<ul><li>写回策略提升突发写性能</li></ul></li><li>负面影响<ul><li>写回策略引起数据的不一致性</li><li>$cache$装满后,写性能降低</li></ul></li></ul><h5 id="②cache命中率的影响因素"><a href="#②cache命中率的影响因素" class="headerlink" title="②cache命中率的影响因素"></a>②cache命中率的影响因素</h5><ul><li><p>命中率:设$N_c$表示$cache$完成存取访问的总次数,$N_m$表示主存完成存取访问的总次数,则命中率$h=\frac{N_c}{N_c+N_m}$</p></li><li><p>平均访问时间$t_a$:设$t_c$表示命中$cache$时的访问时间,$t_m$表示命中主存时的访问时间,则平均访问时间$t_a=ht_c+(1-h)t_m$</p></li><li><p>访问效率:$t_c/t_a$</p></li><li><p>$cache$容量越大,命中率越高</p></li><li><p>块的大小与命中率</p><ul><li><p>块大小越大,块数量越少,空间局部性越好,时间局部性不佳,缺失率提升,具体关系如下图</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%9D%97%E5%AE%B9%E9%87%8F%E4%B8%8E%E5%91%BD%E4%B8%AD%E7%8E%87.png!blogimg" class="" width="400" title="块容量与命中率"></li><li><p>极端情况下,当一个$cache$仅有一块时,命中率急剧下降</p></li></ul></li><li><p>地址映射与命中率:全相联$>$组相联$>$直接相联</p></li></ul><h3 id="5-虚拟存储器"><a href="#5-虚拟存储器" class="headerlink" title="5.虚拟存储器"></a>5.虚拟存储器</h3><h4 id="1-工作原理"><a href="#1-工作原理" class="headerlink" title="(1)工作原理"></a>(1)工作原理</h4><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%AE%9E%E5%9C%B0%E5%9D%80%E4%B8%8E%E8%99%9A%E5%9C%B0%E5%9D%80%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F.png!blogimg" class="" width="400" title="实地址与虚地址计算机系统"><ul><li><p>如左图所示,在实地址计算机系统中,程序直接访问和操作的都是<u>物理内存</u></p><ul><li>用户程序可以访问任意内存,寻址内存的每个字节,很容易破坏操作系统,造成操作系统崩溃;</li><li>同时运行多个程序特别困难,多个程序可能会对同一个物理地址对应的存储单元进行操作,使得程序崩溃。</li></ul></li><li><p>而如右图所示,现代处理器使用<u>虚拟寻址(Virtual Addressing)</u>方式</p><ul><li>使用虚拟寻址方式,$CPU$需要将虚拟地址翻译成物理地址,才能访问到真实的物理内存;</li><li>当在查找表中找到虚拟地址对应的表项时,即可得到物理地址;</li><li>当未找到虚拟地址对应的表项时,需要从外存中调入相应数据到内存中。</li></ul></li><li><p>基于局部性原理的虚拟存储器</p><ul><li>基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其他部分留在外存,就可以启动程序执行;</li><li>由于外存往往比内存大很多,所以待运行程序所需的内存大小可以比物理内存容量大;</li><li>在程序执行过程中,当所访问的信息不在内存时,由<u>操作系统</u>将所需要的部分调入内存,然后继续执行程序;</li><li>另一方面,<u>操作系统</u>将内存中暂时不使用的内容换到外存上,从而腾出空间存放将要调入内存的信息。</li><li>这样,计算机好像为用户提供了一个比物理内存大得多的存储器,这也就是<strong>虚拟存储器</strong>。</li></ul></li><li><p>虚拟内存的好处</p><ul><li>通过虚拟内存可以让程序拥有<u>超过系统物理内存大小的可用内存空间</u>;</li><li>虚拟内存为每个进程提供了一个一致的、私有的地址空间,使得<strong>在进程端看来,其拥有一片连续完整的内存空间</strong>,以更加有效地管理内存并减少出错;</li><li>实际上,应用程序使用的内存空间通常是<strong>被分隔成多个物理内存碎片</strong>,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。</li></ul></li><li><p>虚拟存储器的特点</p><ul><li>虚存处于<strong>主存-辅存存储层次</strong>;</li><li>作用:解决主存容量不足的问题,为程序员提供比主存空间大的编程空间;</li><li>分类:<u>页式虚拟存储器</u>、段式虚拟存储器、段页式虚拟存储器;</li><li>需要解决的问题<ul><li><p>$CPU$访问存储系统的地址属性</p><ul><li>使用虚存后,辅存的访问方式是逻辑地址空间,而主存的访问方式是物理地址空间</li><li>采用$MMU(Memory\enspace Management\enspace Unit)$管理虚拟地址和物理地址</li></ul></li><li><p>判断$CPU$访问的信息是否在主存中</p><ul><li>当不在主存上时,需要将逻辑地址空间中的数据调入到主存中</li><li>采用<u><strong>页表</strong></u>判断$CPU$将访问的信息是否在主存,并与$MMU$配合实现逻辑地址与物理地址之间的转换</li><li>页表存放在<strong>主存</strong>中</li></ul></li></ul></li></ul></li><li><p>虚拟存储器的地址划分</p><ul><li>虚拟地址由虚页号和页偏移量组成;</li><li>虚页号与页表项数有关,页偏移量与物理页大小有关;</li><li>如果主存页大小为$4K$,虚存大小为$4GB$,则页内偏移量为$12$位,虚拟页号为$32-12=20$位,对应的页表有$1024*1024$项。</li></ul></li></ul><h4 id="2-地址映射与变换"><a href="#2-地址映射与变换" class="headerlink" title="(2)地址映射与变换"></a>(2)地址映射与变换</h4><h5 id="①虚拟存储系统的层次结构"><a href="#①虚拟存储系统的层次结构" class="headerlink" title="①虚拟存储系统的层次结构"></a>①虚拟存储系统的层次结构</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E8%99%9A%E6%8B%9F%E5%AD%98%E5%82%A8%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="虚拟存储系统的层次结构"><p><strong>CPU不能用虚拟地址访问cache,因为虚拟地址不具有唯一性。</strong></p><h5 id="②虚拟存储器的实现"><a href="#②虚拟存储器的实现" class="headerlink" title="②虚拟存储器的实现"></a>②虚拟存储器的实现</h5><ul><li>程序执行时,按需载入代码和数据(节省空间)</li><li>虚存机制由硬件与操作系统协作实现<ul><li><u><strong>硬件</strong></u>$(MMU)$将虚拟地址转化为物理地址</li><li>缺页时,由<u><strong>操作系统</strong></u>进行主存和磁盘之间的信息交换</li></ul></li></ul><h5 id="③虚拟存储器与-cache-的相似之处"><a href="#③虚拟存储器与-cache-的相似之处" class="headerlink" title="③虚拟存储器与$cache$的相似之处"></a>③虚拟存储器与$cache$的相似之处</h5><ul><li>将程序中常用的部分驻留在高速存储器<ul><li>程序载入都是<strong>按需载入</strong>(不是全部载入)</li><li>$cache$<strong>分块</strong>,虚拟存储器<strong>分页</strong></li><li>$cache$空间满,需要将不常用的数据<strong>淘汰</strong>到主存中</li><li>主存空间满,需要将不常用程序或数据<strong>淘汰</strong>或<strong>交换</strong>到辅存中</li></ul></li><li>数据调度由硬件(对于$cache$)或操作系统(对于悉尼存储器)完成,对用户透明</li></ul><h5 id="④虚拟存储器与-cache-的不同之处"><a href="#④虚拟存储器与-cache-的不同之处" class="headerlink" title="④虚拟存储器与$cache$的不同之处"></a>④虚拟存储器与$cache$的不同之处</h5><ul><li><p>设计初衷</p><ul><li>$cache$使存储系统的<u>性能</u>接近于高速存储器</li><li>虚拟存储器扩充主存<u>容量</u>,降低价格成本</li></ul></li><li><p>虚存中未命中性能损失远大于$cache$系统</p><ul><li><strong>这是因为缺页处理需要访问磁盘,而$cache$缺失只需要访问主存,带来的开销远小于缺页。</strong></li><li>使用全相联提升命中率</li><li>使用更大的交换单位页</li><li>使用近似$LRU$算法 ($CLOCK$算法)</li></ul></li><li><p>虚存由硬件和$OS$联合管理,$cache$由硬件管理</p></li></ul><h4 id="3-页式虚拟存储器"><a href="#3-页式虚拟存储器" class="headerlink" title="(3)页式虚拟存储器"></a>(3)页式虚拟存储器</h4><h5 id="①页式虚拟存储器的结构"><a href="#①页式虚拟存储器的结构" class="headerlink" title="①页式虚拟存储器的结构"></a>①页式虚拟存储器的结构</h5><ul><li>虚拟地址不具有唯一性,对于不同的进程,存在不同的页表;</li><li>对于相同的虚拟地址,不同的进程访问到的物理地址不同;</li><li>将虚拟地址转换为物理地址时,需要根据$MMU$中的<strong>页表基地址寄存器</strong>获取当前进程对应的<strong>页表基地址</strong>;</li><li>再根据$VPN$获取页表项地址$PTEA$,由此访问主存中的页表得到页表项;</li><li>当页表项的$Valid$为$1$时,表明要访问的页<u><strong>在</strong></u>内存中,则可得到主存中的物理页号$PPN$,与页内偏移组合后得到物理地址$PA$;</li><li>当页表项的$Valid$为$0$时,表明要访问的页<u><strong>不在</strong></u>内存中,则需要调用缺页中断处理程序,将外存中的页调入到主存中,并更新页表。</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E9%A1%B5%E5%BC%8F%E8%99%9A%E6%8B%9F%E5%AD%98%E5%82%A8%E5%99%A8%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="页式虚拟存储器结构"><h5 id="②虚拟地址-rightarrow-物理地址(页命中)"><a href="#②虚拟地址-rightarrow-物理地址(页命中)" class="headerlink" title="②虚拟地址$\rightarrow$物理地址(页命中)"></a>②虚拟地址$\rightarrow$物理地址(页命中)</h5><ul><li>$CPU$给出虚拟地址$VA$</li><li>$MMU$根据页表基地址寄存器和虚拟页号$VPN$,得到页表项地址$PTEA$;</li><li>根据页表项地址在<u><strong>内存</strong></u>中找到页表项$PTA$返回给$MMU$;</li><li>$MMU$根据$PTA$中给出的$PPN$和页内偏移$VPO$,得到物理地址$PA$;</li><li>根据$PA$访问存储器返回数据给$CPU$;</li><li>其中操作$2)$和$3)$均进行访存操作。</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80%E5%88%B0%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80%EF%BC%88%E9%A1%B5%E5%91%BD%E4%B8%AD%EF%BC%89.png!blogimg" class="" width="400" title="虚拟地址到物理地址(页命中)"><h5 id="③虚拟地址-rightarrow-物理地址(缺页)"><a href="#③虚拟地址-rightarrow-物理地址(缺页)" class="headerlink" title="③虚拟地址$\rightarrow$物理地址(缺页)"></a>③虚拟地址$\rightarrow$物理地址(缺页)</h5><ul><li>$CPU$给出虚拟地址$VA$</li><li>$MMU$根据页表基地址寄存器和虚拟页号$VPN$,得到页表项地址$PTEA$;</li><li>根据页表项地址在<u><strong>内存</strong></u>中找到页表项$PTA$返回给$MMU$;</li><li>$MMU$根据$PTA$中的$Valid$位发现缺页,抛出异常,执行缺页异常处理程序;</li><li>如果页表已满,需要调出页后再从磁盘调入页到主存中,并更新页表;</li><li><strong>$cache$可以采用写穿策略,而页表总是采用写回策略,这是因为写穿策略需要同时写快存与慢存,而写磁盘比写主存慢得多。</strong></li><li>返回到第一步重新进行操作。</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80%E5%88%B0%E7%89%A9%E7%90%86%E5%9C%B0%E5%9D%80%EF%BC%88%E7%BC%BA%E9%A1%B5%EF%BC%89.png!blogimg" class="" width="400" title="虚拟地址到物理地址(缺页)"><h5 id="④包含-cache-的虚存-主存层次结构"><a href="#④包含-cache-的虚存-主存层次结构" class="headerlink" title="④包含$cache$的虚存-主存层次结构"></a>④包含$cache$的虚存-主存层次结构</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%8C%85%E5%90%ABcache%E7%9A%84%E8%99%9A%E5%AD%98-%E4%B8%BB%E5%AD%98%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="包含cache的虚存-主存层次结构"><ul><li>将主存与$cache$拆分开来,由于$cache$会缓存主存中经常访问的数据,因此<strong>部分页表块会被作为热数据调度到cache中</strong></li><li>首先根据$MMU$生成的$PTEA$访问$cache$,如果页表项$PTE$命中,则直接返回$PTE$生成物理地址访问数据</li><li>如果访问$cache$时,页表项缺失,需要将$PTE$所在页表块从主存调度到$cache$中</li><li>使用$PA$访问$cache$时,也可能存在$cache$<strong>缺失问题</strong>,这时需要根据$cache$工作原理调度数据块到$cache$中</li></ul><h5 id="⑤快表-TLB"><a href="#⑤快表-TLB" class="headerlink" title="⑤快表$TLB$"></a>⑤快表$TLB$</h5><ul><li>普通情况下,地址转换速度慢<ul><li>访问页表,访问数据,需$2$次访存,速度慢</li><li>为缩小页表大小,$OS$普遍采用多级页表结构,速度更慢</li></ul></li><li>$cache$虽然可以缓存部分页表块,但这种数据块的粒度较大,不能充分利用局部性</li><li>引入一个体积小的快表$TLB$<ul><li>本质上是容量较小的$cache$</li><li>引入相联存储器机制,提高查找速度</li><li>采用随机替换算法</li><li>缓存页表中经常被访问的表项——$(Valid,VPN,PPN)$</li></ul></li></ul><h5 id="⑥包含-TLB-表时的虚-实转换流程"><a href="#⑥包含-TLB-表时的虚-实转换流程" class="headerlink" title="⑥包含$TLB$表时的虚-实转换流程"></a>⑥包含$TLB$表时的虚-实转换流程</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E5%8C%85%E5%90%ABTLB%E8%A1%A8%E6%97%B6%E7%9A%84%E8%99%9A-%E5%AE%9E%E8%BD%AC%E6%8D%A2%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="包含TLB表时的虚-实转换流程"><h5 id="⑦详细的虚-实转换流程"><a href="#⑦详细的虚-实转换流程" class="headerlink" title="⑦详细的虚-实转换流程"></a>⑦详细的虚-实转换流程</h5><p>如图所示,最快的访问流程为<strong>最左侧直线型访问流程</strong>,此时访问性能最优</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-3/Chp4-%E8%AF%A6%E7%BB%86%E7%9A%84%E8%99%9A-%E5%AE%9E%E8%BD%AC%E6%8D%A2%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="详细的虚-实转换流程"><ol><li><p>$CPU$给出虚拟地址$VA$</p></li><li><p>使用$VA$访问$TLB$,如果$TLB$<strong>命中</strong>,得到$PPN$,进而得到物理地址访问数据</p><ul><li>首先使用物理地址访问$cache$,如果命中返回数据</li><li>如果缺失,则进行$cache$缺失处理</li></ul></li><li><p>如果$TLB$<strong>缺失</strong>,则使用$MMU$根据$VA$生成的$PTEA$访问$cache$中的页表块</p><ul><li>如果$cache$<strong>页表块命中</strong>,则得到$PTE$</li><li>如果$cache$<strong>页表块缺失</strong>,则使用$PTEA$访问主存页表得到$PTE$</li></ul></li><li><p>得到$PTE$后,由有效位判断是否<strong>页命中</strong></p><ul><li>如果<strong>页命中</strong>,根据$PPN$更新$TLB$表项,使用物理地址访问$cache$</li><li>如果<strong>页缺失</strong>,调用缺页异常处理程序,载入页到主存中,更新$TLB$,<strong>重启缺页命令</strong></li></ul></li><li><p>具体的$TLB$命中、$cache$<strong>数据块</strong>命中、页命中组合如下表</p><table><thead><tr><th align="center">序号</th><th align="center">$TLB$</th><th align="center">$cache$数据块</th><th align="center">页</th><th align="center">可能性</th><th>说明</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">命中</td><td align="center">命中</td><td align="center">命中</td><td align="center">可能</td><td>$TLB$命中,则页一定命中,此时$cache$可能缺失,也可能命中</td></tr><tr><td align="center">2</td><td align="center">命中</td><td align="center">缺失</td><td align="center">命中</td><td align="center">可能</td><td>同情况$1$</td></tr><tr><td align="center">3</td><td align="center">缺失</td><td align="center">命中</td><td align="center">命中</td><td align="center">可能</td><td>$TLB$缺失后还可以访问慢速页表,页可能命中,$cache$原因同情况$1$</td></tr><tr><td align="center">4</td><td align="center">缺失</td><td align="center">缺失</td><td align="center">命中</td><td align="center">可能</td><td>同情况$3$</td></tr><tr><td align="center">5</td><td align="center">缺失</td><td align="center">缺失</td><td align="center">缺失</td><td align="center">可能</td><td>最<strong>糟糕</strong>的情况,虚存系统初始化时常见</td></tr><tr><td align="center">6</td><td align="center">命中</td><td align="center">缺失</td><td align="center">缺失</td><td align="center">不可能</td><td>页不在主存中,$TLB$一定不包含其页表项,$TLB$不可能命中</td></tr><tr><td align="center">7</td><td align="center">命中</td><td align="center">命中</td><td align="center">缺失</td><td align="center">不可能</td><td>同情况$6$</td></tr><tr><td align="center">8</td><td align="center">缺失</td><td align="center">命中</td><td align="center">缺失</td><td align="center">不可能</td><td>页不在主存中,对应数据也不可能在$cache$中,$cache$不可能命中</td></tr></tbody></table></li></ol>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机组成原理 </tag>
</tags>
</entry>
<entry>
<title>计算机组成原理笔记(四)</title>
<link href="/posts/2022/06/12/notes/Principles-of-Computer-Composition-4/"/>
<url>/posts/2022/06/12/notes/Principles-of-Computer-Composition-4/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="五、指令系统"><a href="#五、指令系统" class="headerlink" title="五、指令系统"></a>五、指令系统</h2><h3 id="1-指令格式"><a href="#1-指令格式" class="headerlink" title="1.指令格式"></a>1.指令格式</h3><ul><li><p>指令字:表示一条指令的机器字,简称指令</p></li><li><p>指令格式:用二进制代码表示指令的结构形式</p><blockquote><p>指令需要解决的问题:</p><p>操作数——指令要求计算机处理什么数据;</p><p>操作码——指令要求计算机对数据做什么处理;</p><p>寻址方式——计算机怎样才能得到要处理的数据</p></blockquote></li><li><p>指令格式的具体构成</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E6%8C%87%E4%BB%A4%E6%A0%BC%E5%BC%8F.png!blogimg" class="" width="400" title="指令格式"><ul><li>操作码字段长度决定指令系统规模<ul><li>每条指令对应一个操作码</li><li>定长操作码:$Length_{OP}=\lceil log_2n\rceil$</li><li>变长操作码:操作码<strong>向不用的地址码字段扩展</strong></li></ul></li><li>操作数字段可能有多个<ul><li>寻址方式字段:长度与寻址方式种类有关,也可能隐含在操作码字段</li><li>地址码字段:作用及影响、长度和寻址方式有关</li></ul></li></ul></li><li><p>指令字长度</p><ul><li>指令字长度:指令中包含二进制代码的位数</li><li>字长与机器字的长度有关: 单字长,双字长,半字长<ul><li>指令字越长,地址码长度越长,可直接寻址空间越大</li><li>指令字越长,占用空间越大,取指令越慢</li></ul></li><li>定长指令: 结构简单,控制线路简单,如$MIPS$指令</li><li>变长指令: 结构灵活,充分利用指令长度,控制复杂,如$X86$指令</li></ul></li><li><p>指令地址码</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E4%B8%8D%E5%90%8C%E7%9A%84%E6%8C%87%E4%BB%A4%E5%9C%B0%E5%9D%80%E7%A0%81.png!blogimg" class="" width="400" title="不同的指令地址码"><ul><li>$3$种指令操作码部分不得重叠,否则无法区分;</li><li>设双操作数地址码长度为$8$,每个操作数的长度为$12$,双地址指令数为$k$, 显然$k<2^8$,则$2^8-k$为多余状态,可用于表示其他类型指令,即可用于单操作数指令的条数为$(2^8-k)*2^{12}$,$2^{12}$是多余$12$位组合;</li><li>例:设某指令系统指令字长$16$位,每个地址码为$6$位。若要求设计二地址指令$15$条、一地址指令$34$条,则最多还可设计多少条零地址指令。</li></ul></li></ul><h3 id="2-寻址方式"><a href="#2-寻址方式" class="headerlink" title="2.寻址方式"></a>2.寻址方式</h3><h4 id="1-指令寻址"><a href="#1-指令寻址" class="headerlink" title="(1)指令寻址"></a>(1)指令寻址</h4><h5 id="①顺序寻址"><a href="#①顺序寻址" class="headerlink" title="①顺序寻址"></a>①顺序寻址</h5><ul><li>程序计数器$(PC)$对指令序号进行计数</li><li><u><strong>程序计数器对程序员可见</strong></u></li><li>$PC$存放下条指令地址,初始值为程序首址</li><li>执行一条指令时,$PC$=$PC$+<u><strong>当前指令字节长度</strong></u></li><li>综上,也即$Mem[PC++]\rightarrow IR$</li></ul><h5 id="②跳跃寻址"><a href="#②跳跃寻址" class="headerlink" title="②跳跃寻址"></a>②跳跃寻址</h5><ul><li>下条指令地址不是$PC++$得到,而是由指令本身给出</li><li>跳跃的处理方式是重新修改$PC$的内容,然后进入取指令阶段</li><li>注意偏移量可能为正,也可能为<strong>负</strong>,偏移量应以$PC++$后的值计算</li></ul><h4 id="2-操作数寻址"><a href="#2-操作数寻址" class="headerlink" title="(2)操作数寻址"></a>(2)操作数寻址</h4><ul><li><p>立即寻址:地址码字段是操作数本身</p><p>如$MOV\enspace AX,38H$对应指令如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E7%AB%8B%E5%8D%B3%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="立即寻址"></li><li><p>寄存器寻址:操作数在$CPU$的内部寄存器中</p><p>如$PUSH\enspace AX$对应指令如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E5%AF%84%E5%AD%98%E5%99%A8%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="寄存器寻址"></li><li><p>直接寻址:地址码字段直接给出<strong>操作数在内存的地址</strong></p><p>如$INC\enspace [200]$对应指令如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E7%9B%B4%E6%8E%A5%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="直接寻址"></li><li><p>间接寻址:$D$单元的内容是操作数地址, $D$是操作数地址的地址</p><p>如下图示意:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E9%97%B4%E6%8E%A5%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="间接寻址"></li><li><p>寄存器间接寻址:$D$单元的内容是操作数的地址,$R$是操作数地址的地址</p><p>如$INC\enspace [BX]$对应指令如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E5%AF%84%E5%AD%98%E5%99%A8%E9%97%B4%E6%8E%A5%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="寄存器间接寻址"></li><li><p>相对寻址:指令中的$D$加上$PC$的内容作为操作数的地址</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E7%9B%B8%E5%AF%B9%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="相对寻址"></li><li><p>基址$/$变址寻址:操作数地址为基址$/$变址寄存器$+$偏移量</p><p>如$MOV\enspace AX,32[SI]$对应指令如下:</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E5%9F%BA%E5%9D%80%2B%E5%8F%98%E5%9D%80%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="基址+变址寻址"><ul><li>基址寄存器一般不修改</li><li>$SI,DI$都称为变址寄存器</li></ul></li><li><p>寻址对比</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E5%AF%BB%E5%9D%80%E5%AF%B9%E6%AF%94.png!blogimg" class="" width="400" title="寻址对比"></li><li><p>寻址方式举例</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F%E4%B8%BE%E4%BE%8B.png!blogimg" class="" width="400" title="寻址方式举例"></li></ul><h3 id="3-指令格式设计"><a href="#3-指令格式设计" class="headerlink" title="3.指令格式设计"></a>3.指令格式设计</h3><ul><li><p>设计步骤</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E6%8C%87%E4%BB%A4%E6%A0%BC%E5%BC%8F%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="指令格式模型"><ul><li>根据<strong>指令规模及是否支持操作码扩展</strong>,确定操作码字段长度</li><li>根据<strong>对操作数的要求</strong>确定地址码字段的个数</li><li>根据<strong>寻址方式的要求</strong>,为各地址码字段确定寻址方式字段长度</li><li>确定定长指令还是变长指令</li></ul></li><li><p>设计举例</p><ul><li><p>字长$16$位,主存$64K$,指令单字长单地址,$80$条指令。寻址方式有直接、间接、相对、变址。请设计指令格式。</p></li><li><p>某机字长$32$位,采用三地址指令,支持$8$种寻址操作,完成$60$种操作,各寻址方式均可在$2K$主存范围内取得操作数,并可在$1K$范围内保存运算结果。问应采用什么样的指令格式?指令字长最少应为多少位?执行一条直接寻址模式指令最多要访问多少次主存?</p></li><li><p>分析以下指令格式及寻址方式特点?</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-%E6%8C%87%E4%BB%A4%E5%88%86%E6%9E%90%E4%B8%BE%E4%BE%8B.png!blogimg" class="" width="400" title="指令分析举例"><ul><li>1)二地址指令;</li><li>2)操作码可指定$16$条指令;</li><li>3)源和目的均有$8$种寻址方式;</li><li>4)源地址寄存器和目的地址寄存器均有$8$个;</li><li>5)可寻址范围为$1\sim 64K$(与机器字长有关)</li></ul></li></ul></li></ul><h3 id="4-MIPS-指令系统"><a href="#4-MIPS-指令系统" class="headerlink" title="4.$MIPS$指令系统"></a>4.$MIPS$指令系统</h3><h4 id="1-CISC-与-RISC"><a href="#1-CISC-与-RISC" class="headerlink" title="(1)$CISC$与$RISC$"></a>(1)$CISC$与$RISC$</h4><ul><li>$CISC$ $(Complex\enspace Instruction\enspace System\enspace Computer)$<ul><li>复杂指令系统计算机;</li><li>指令数量多,指令功能,复杂的计算机;</li><li>$Intel\enspace X86$</li></ul></li><li>$RISC(Reduced\enspace Instruction\enspace System\enspace Computer)$<ul><li>精简指令系统计算机;</li><li>指令数量少,指令功能单一的计算机;</li><li>$1982$年后的指令系统基本都是$RISC$;</li><li>$MIPS,RISC-V$</li></ul></li><li>$RISC$的特点<ul><li>指令条数少,只保留使用频率最高的简单指令,指令定长<ul><li>便于硬件实现,用软件实现复杂指令功能</li></ul></li><li>$Load/Store$架构<ul><li>只有存/取数指令才能访问存储器,其余指令的操作都在寄存器之间进行</li><li>便于硬件实现</li></ul></li><li>指令长度固定,指令格式简单、寻址方式简单<ul><li>便于硬件实现</li></ul></li><li>$CPU$设置大量寄存器$(32\sim 192)$<ul><li>便于编译器实现</li></ul></li><li>一个机器周期完成一条机器指令</li><li>$RISC\enspace CPU$采用硬布线控制,$CISC$采用微程序</li></ul></li></ul><h4 id="2-MIPS-指令概述"><a href="#2-MIPS-指令概述" class="headerlink" title="(2)$MIPS$指令概述"></a>(2)$MIPS$指令概述</h4><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-MIPS%E6%8C%87%E4%BB%A4%E6%A6%82%E8%BF%B0.png!blogimg" class="" width="400" title="MIPS指令概述"><h4 id="3-MIPS-寻址方式"><a href="#3-MIPS-寻址方式" class="headerlink" title="(3)$MIPS$寻址方式"></a>(3)$MIPS$寻址方式</h4><ul><li><p>立即数寻址</p></li><li><p>寄存器直接寻址</p></li><li><p>基址寻址</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-MIPS%E5%9F%BA%E5%9D%80%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="MIPS基址寻址"><ul><li>使用基址寻址的指令:$lw ,sw, lh, sh, lb, lbu$等</li></ul></li><li><p>相对寻址</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-MIPS%E7%9B%B8%E5%AF%B9%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="MIPS相对寻址"><ul><li>使用用相对寻址的指令:$beq, bne$</li></ul></li><li><p>伪直接寻址(页面寻址)</p></li></ul> <img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-MIPS%E4%BC%AA%E7%9B%B4%E6%8E%A5%E5%AF%BB%E5%9D%80.png!blogimg" class="" width="400" title="MIPS伪直接寻址"><ul><li>使用伪直接寻址的指令:$j,jal$</li></ul><h4 id="3-R-型指令"><a href="#3-R-型指令" class="headerlink" title="(3)$R$型指令"></a>(3)$R$型指令</h4><ul><li>指令格式</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-R%E6%8C%87%E4%BB%A4.png!blogimg" class="" width="400" title="R指令"><ul><li>指令举例</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-R%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B.png!blogimg" class="" width="400" title="R指令举例"><h4 id="3-I-型指令"><a href="#3-I-型指令" class="headerlink" title="(3)$I$型指令"></a>(3)$I$型指令</h4><ul><li><p>指令格式</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-I%E6%8C%87%E4%BB%A4.png!blogimg" class="" width="400" title="I指令"></li><li><p>指令举例</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-I%E6%8C%87%E4%BB%A4%E4%B8%BE%E4%BE%8B.png!blogimg" class="" width="400" title="I指令举例"></li></ul><h4 id="4-J-型指令"><a href="#4-J-型指令" class="headerlink" title="(4)$J$型指令"></a>(4)$J$型指令</h4><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp5-J%E6%8C%87%E4%BB%A4.png!blogimg" class="" width="400" title="J指令"><h2 id="七、输入输出系统"><a href="#七、输入输出系统" class="headerlink" title="七、输入输出系统"></a>七、输入输出系统</h2><h3 id="1-外设与I-x2F-O接口"><a href="#1-外设与I-x2F-O接口" class="headerlink" title="1.外设与I/O接口"></a>1.外设与I/O接口</h3><ul><li><p><u>输入输出设备</u>是计算机与人或者机器系统进行数据交互的装置,用于实现计算机内部二进制信息与外部不同形式信息的转换,简称<strong>外部设备或外设</strong></p></li><li><p>计算机的输入/输出系统</p><ul><li>包括外部设备、接口部件、总线以及相应的<u>管理软件</u>,简称$I/O$系统</li><li>保证$CPU$能够正确选择$I/O$设备并实现对其控制,与数据传输</li><li>利用数据缓冲、合适的数据传送方式,实现主机外设间速度匹配</li></ul></li><li><p>$I/O$接口</p><ul><li><p>连接总线与$I/O$设备的物理和逻辑界面</p></li><li><p>既包括物理连接电路,也包括软件逻辑接口</p></li><li><p>所有设备均通过$I/O$接口(总线接口)与总线相连</p></li><li><p>$CPU$使用设备地址经总线与$I/O$接口通信访问$I/O$设备</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-IO%E6%8E%A5%E5%8F%A3%E7%BB%93%E6%9E%84.png!blogimg" class="" width="400" title="IO接口结构"></li></ul></li><li><p>$I/O$接口编址</p><ul><li>统一编址<ul><li>内存映射编址$(Memory-mapped)$</li><li>外设地址与内存地址统一编址,<u><strong>同一个地址空间</strong></u></li><li><u><strong>不需要</strong></u>设置专用的$I/O$指令</li><li><u><strong>采用访存指令访问外设</strong></u>,具体访问什么设备取决于地址</li><li>如$MIPS$指令系统</li></ul></li><li>独立编址<ul><li>端口映射编址$(Port-mapped)$</li><li>$I/O$地址空间与主存地址空间<u><strong>相互独立</strong></u></li><li>$I/O$地址又称为$I/O$端口</li><li><u><strong>不同设备中的不同寄存器和存储器都有唯一的端口地址</strong></u></li><li><strong>使用$I/O$指令访问外设</strong></li><li>如$X86$指令系统中的$OUT$和$IN$指令</li></ul></li></ul></li><li><p>$I/O$接口软件</p><ul><li>现代计算机中用户必须<u><strong>通过操作系统间接访问设备</strong></u>,屏蔽设备细节,使用更方便</li><li>与$OS$无关的$I/O$库 (用户态)<ul><li>如$C$语言中的标准$I/O$库$stdio.h$</li><li>printf、scanf、getchar、putchar、fopen、fclose等</li><li>用户程序主要通过调用$I/O$库访问设备,<strong>方便程序在不同$OS$间移植</strong></li></ul></li><li>与设备无关的$OS$调用库(内核态)<ul><li>open、read、write、seek、ioctl、close</li></ul></li><li>独立的设备驱动程序(内核态)<ul><li>设备驱动程序是与设备相关的$I/O$软件部分</li><li>不同设备对应不同的驱动程序</li><li>遵循具体设备的$I/O$接口约定,包含设备接口细节</li></ul></li></ul></li></ul><h3 id="2-程序查询方式"><a href="#2-程序查询方式" class="headerlink" title="2.程序查询方式"></a>2.程序查询方式</h3><ul><li><p>程序查询方式中$CPU$与$I/O$设备的交互完全通过$CPU$执行程序完成;</p></li><li><p>程序查询方式分为独占查询和定时查询;</p></li><li><p>独占查询方式</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E7%8B%AC%E5%8D%A0%E6%9F%A5%E8%AF%A2.png" class="" width="600" title="独占查询"><ul><li>假设$CPU$执行某用户程序时,需要访问$I/O$设备;</li><li>这时,$CPU$通过对$I/O$设备发送命令参数以<strong>启动设备</strong>;</li><li>设备收到启动命令后,开始相对于$CPU$速度而言很漫长的<strong>数据准备</strong>过程;</li><li>从启动设备到设备就绪的时间,$CPU$只能<strong>反复读取设备的状态寄存器</strong>,判断设备是否就绪;</li><li>如果没有就绪则继续读取状态字,这种方式称为轮询等待$(busy-waiting)$;</li><li>一旦设备就绪,用户程序就可以通过相应的$I/O$指令读取特定端口中的数据,完成<strong>实际的数据传输</strong>;</li><li><strong>一次轮询只能完成一个数据单元的传输</strong>;</li><li>$CPU$浪费了大量的时间进行查询,对于慢速设备,这种方式并不合适;</li><li>而对于高速设备或者简单设备,这种方式较为合适。</li></ul></li><li><p>定时查询方式</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E5%AE%9A%E6%97%B6%E6%9F%A5%E8%AF%A2.png" class="" width="600" title="定时查询"><ul><li>应用<strong>定时中断</strong>技术;</li><li>当$CPU$启动设备时,同时启动一个定时器;</li><li>设备准备数据的过程中,$CPU$<strong>不再轮询等待</strong>,而是进行<u><strong>进程调度</strong></u>,将当前进程加入$I/O$等待队列,并调度新的用户进程$P_2$运行;</li><li>定时器时间到时,产生<strong>定时中断</strong>,定时中断会触发$CPU$中断用户进程$P_2$或者其他进程,而去执行定时中断服务程序;</li><li>中断服务程序的任务是<strong>查询设备状态</strong>,如果设备没有准备就绪,继续启动<strong>新的定时中断</strong>,如果设备准备就绪,则唤醒进程$P_1$,中断服务完毕,继续执行用户进程$P_2$;随着时间片的轮转,用户进程$P_1$会被进程调度程序重新调度运行,该程序会完成实际数据传输。</li><li><u><strong>需要设定定时中断的计时时长</strong></u></li></ul></li></ul><h3 id="3-程序中断方式"><a href="#3-程序中断方式" class="headerlink" title="3.程序中断方式"></a>3.程序中断方式</h3><h4 id="1-工作原理"><a href="#1-工作原理" class="headerlink" title="(1)工作原理"></a>(1)工作原理</h4><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E4%B8%AD%E6%96%AD%E6%8E%A7%E5%88%B6.png" class="" width="600" title="中断控制"><ul><li>提高了$CPU$的使用效率<ul><li>主动告知机制避免了反复查询设备状态</li><li>仍需$CPU$占用(中断服务子程序运行时间+中断开销)</li></ul></li><li>适合随机出现的服务</li><li>需要专门的硬件</li></ul><h4 id="2-中断实现"><a href="#2-中断实现" class="headerlink" title="(2)中断实现"></a>(2)中断实现</h4><h5 id="①中断原理"><a href="#①中断原理" class="headerlink" title="①中断原理"></a>①中断原理</h5><ul><li>$CPU$暂时中止现行程序的执行,转去执行为某个<u>随机事件</u>服务的中断处理子程序,处理完后自动<u>恢复原程序的执行</u>;</li><li>中断执行时间:一条指令执行完毕后进入公操作,以检查是否需要响应中断</li></ul><h5 id="②中断处理流程"><a href="#②中断处理流程" class="headerlink" title="②中断处理流程"></a>②中断处理流程</h5><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E4%B8%AD%E6%96%AD%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="中断处理流程"><h5 id="③中断优先级"><a href="#③中断优先级" class="headerlink" title="③中断优先级"></a>③中断优先级</h5><ul><li><p>多设备同时产生中断请求时,如何处理?</p><ul><li><p>优先级高的先响应,优先级低的后响应</p></li><li><p>$CPU$优先级随不同中断服务程序而改变</p><p>执行某设备中断服务子程序,$CPU$优先级就与该设备的优先级一样</p></li><li><p>中断嵌套:优先级高的中断请求<strong>可以</strong>中断优先级低的程序</p></li></ul></li><li><p>举例:设优先级$A>B>C$</p><ul><li><p>单级中断</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E5%8D%95%E7%BA%A7%E4%B8%AD%E6%96%AD.png!blogimg" class="" width="400" title="单级中断"></li><li><p>多重中断</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E5%A4%9A%E9%87%8D%E4%B8%AD%E6%96%AD.png!blogimg" class="" width="400" title="多重中断"></li></ul></li><li><p>划分优先级的规律</p><ul><li><strong>硬件故障中断属于最高级,其次是程序错误中断</strong></li><li><strong>非屏蔽中断</strong>优先于可屏蔽中断</li><li><strong>$DMA$请求</strong>优先于$I/O$设备传送的中断请求</li><li><strong>高速设备</strong>优先于低速设备</li><li><strong>输入设备</strong>优先于输出设备</li><li><strong>实时设备</strong>优先于普通设备</li></ul></li></ul><h5 id="④中断屏蔽"><a href="#④中断屏蔽" class="headerlink" title="④中断屏蔽"></a>④中断屏蔽</h5><ul><li><p>响应优先级</p><ul><li>$CPU$对各设备中断请求进行响应,并准备好处理的先后次序</li><li>这种次序在硬件线路上已固定,不便变动</li></ul></li><li><p>处理优先级</p><ul><li>$CPU$实际对各中断请求处理的先后次序</li><li><strong>如果不使用屏蔽技术,响应的优先次序就是处理的优先次序</strong></li><li>中断屏蔽技术可动态改变各设备的处理优先级</li></ul></li><li><p>中断屏蔽方式</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E4%B8%AD%E6%96%AD%E5%B1%8F%E8%94%BD%E6%96%B9%E5%BC%8F.png!blogimg" class="" width="400" title="中断屏蔽方式"><ul><li>中断请求寄存器$IRR$<ul><li>对应位为$1$表示相应外设发出了中断请求</li><li>中断字,中断码</li></ul></li><li>中断屏蔽寄存器$INM$<ul><li>对应位为$1$表示对相应设备设置屏蔽,否则取消屏蔽</li><li>每个设备都有自己独立的中断屏蔽字</li><li>$CPU$执行某设备的中断服务子程序时将其中断屏蔽字载入$INM$</li><li><strong>不可屏蔽中断不受中断屏蔽寄存器的控制</strong></li></ul></li><li>中断允许触发器$IE$<ul><li>允许中断源中断$CPU$</li></ul></li><li>屏蔽码<ul><li>控制各设备接口的屏蔽触发器,可改变处理次序</li><li>屏蔽码的每一位都表示对某个设备是否进行屏蔽</li><li>运行某个设备的中断服务程序时载入对应的屏蔽码</li><li>某设备的屏蔽码中的$1$的个数越多,表示处理优先级越高</li></ul></li></ul></li></ul><h5 id="⑤中断仲裁"><a href="#⑤中断仲裁" class="headerlink" title="⑤中断仲裁"></a>⑤中断仲裁</h5><ul><li><p>同一时刻可能有多个设备同时发出中断请求,响应哪个中断源?</p><ul><li>链式查询</li><li>独立请求</li><li>中断控制器方式</li><li>分组链式结构</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E4%B8%AD%E6%96%AD%E8%AF%B7%E6%B1%82%E4%BF%A1%E5%8F%B7%E7%9A%84%E4%BC%A0%E8%BE%93%E6%96%B9%E5%BC%8F.png!blogimg" class="" width="400" title="中断请求信号的传输方式"></li><li><p>请求方式</p><ul><li>独立请求:向量中断<ul><li>将服务程序入口(中断向量)组织在中断向量表中;</li><li><strong>硬件查询法</strong>:响应时由硬件直接产生中断号(向量地址),查中断向量表取得服务程序入口,转入相应服务程序。</li></ul></li><li>中断共享:非向量中断<ul><li>将服务程序入口组织在查询程序中;</li><li><strong>程序识别法</strong>:响应时执行查询程序查询中断源,查询特定端口$(GPIO)$识别中断源</li></ul></li></ul></li></ul><h5 id="⑥中断处理实现"><a href="#⑥中断处理实现" class="headerlink" title="⑥中断处理实现"></a>⑥中断处理实现</h5><ul><li><p>中断处理中的问题</p><ul><li>中断响应条件<ul><li>中断允许触发器$IE=1$</li><li>对应的中断未被屏蔽</li><li>无更高优先级的$DMA$请求</li><li>中断嵌套必须优先级更高</li><li>指令已经执行完最后一个机器周期<ul><li>保证指令执行的完整性</li><li>缺页异常的处理时机与此不同</li></ul></li></ul></li><li>保存现场,恢复现场<ul><li>中断程序用到的通用寄存器,$EPC$,屏蔽字</li><li>缺页异常的断点和外部中断断点不一致</li></ul></li><li>中断过程由软硬件结合完成</li></ul></li><li><p>中断控制器</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E4%B8%AD%E6%96%AD%E6%8E%A7%E5%88%B6%E5%99%A8.png!blogimg" class="" width="400" title="中断控制器"></li><li><p>中断控制器处理流程</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-%E4%B8%AD%E6%96%AD%E6%8E%A7%E5%88%B6%E5%99%A8%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="中断控制器处理流程"></li></ul><h3 id="4-DMA-方式"><a href="#4-DMA-方式" class="headerlink" title="4.$DMA$方式"></a>4.$DMA$方式</h3><h4 id="1-基本概念"><a href="#1-基本概念" class="headerlink" title="(1)基本概念"></a>(1)基本概念</h4><ul><li>$DMA$方式与中断方式<ul><li>中断方式<ul><li>传送一个数据执行一次中断服务子程序(几十条指令)</li><li>效率低下,不适合于高速传输的系统</li></ul></li><li>$DMA$方式<ul><li>外设与主存间建立一个由硬件管理的数据通路</li><li>这个数据通路是虚拟通路,仍通过系统总线进行数据传输</li><li>$CPU$不介入外设与主存的数据传送操作</li><li>减少$CPU$开销,提升效率</li></ul></li><li>二者的区别<ul><li>中断通过<strong>程序</strong>传送数据,$DMA$靠<strong>硬件</strong>来实现</li><li>中断时机为<strong>两指令之间</strong>,$DMA$响应时机为<strong>两存储周期之间</strong></li><li>中断不仅具有数据传送能力,还能<strong>处理异常事件</strong>,$DMA$只能进行数据传送</li><li>$DMA$仅挪用了一个存储周期,<strong>不改变$CPU$现场</strong></li><li><strong>$DMA$请求的优先权比中断请求高</strong><ul><li>$CPU$优先响应$DMA$请求,是为了避免$DMA$所连接的高速外设丢失数据</li></ul></li><li><strong>$DMA$利用了中断技术</strong></li></ul></li></ul></li><li>$DMA$方式下的访存冲突<ul><li>冲突原因<ul><li>$DMA$控制器直接访问内存</li><li>$CPU$执行主程序,也需要访问内存</li></ul></li><li>处理冲突<ul><li>停止$CPU$使用主存<ul><li>$DMA$传送数据时,$CPU$停止使用主存</li><li>一批数据传送结束后,$DMA$再交还主存使用权</li><li>$DMA$传送过程中,$CPU$处于<strong>等待</strong>状态</li><li>$DMA$批量数据传输周期过长,$CPU$长期无法访问内存</li><li>由于总线一次只能传输一个机器字,因此批量传输时需要循环传输</li><li>由于外设的访问时间较慢,外设传送两个数据之间存在时间间隔</li><li>而这个<strong>时间间隔大于存储周期</strong>,总线和内存未充分利用</li></ul></li><li>$DMA$与$CPU$交替使用主存<ul><li>每个存储周期分成两段<ul><li>一段用于$DMA$访问主存</li><li>一段用于$CPU$访问主存</li></ul></li><li><strong>无主存使用权移交过程</strong></li></ul></li><li>周期挪用法<ul><li>$DMA$要求访问主存时,$CPU$暂停一个或多个存储周期;</li><li>一个数据传送结束后,$CPU$继续运行;</li><li>$CPU$现场没有变动,仅延缓了指令的执行;</li><li>如发生访存冲突,则$DMA$优先访问</li></ul></li></ul></li></ul></li></ul><h4 id="2-DMA-传输"><a href="#2-DMA-传输" class="headerlink" title="(2)$DMA$传输"></a>(2)$DMA$传输</h4><ul><li><p>$DMA$控制器</p></li><li><p>$DMA$传输流程</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-4/Chp7-DMA%E4%BC%A0%E8%BE%93%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="DMA传输流程"><ul><li><p>准备阶段($CPU$干预)</p><p>主机通过$CPU$指令向$DMA$接口发送必要的传送参数,并启动$DMA$工作。</p><ul><li>数据传送的方向</li><li>数据块在主存的首地址</li><li>数据在外设存储介质上的地址</li><li>数据的传送量</li></ul></li><li><p>传输阶段($CPU$不干预)</p><p>宏观上,$DMA$是连续传送一批数据;微观上,每传送一个数据,发一次$DMA$请求。</p><ul><li>设备准备数据:当设备接收到$CPU$的$DMA$命令后就可以开始准备数据;</li><li>设备发送$DMA$请求:数据准备就绪后,通过$DREQ$控制线向$DMAC$发出$DMA$请求;</li><li>$DMAC$申请总线:$DMAC$收到$DMA$请求后立即将$HOLD$信号置$1$,向$CPU$申请总线控制权;</li><li>总线授权:$CPU$<strong>在机器周期结束后响应总线使用申请</strong>,让出总线控制权,并发出总线授权信号$HLDA$通知 $DMAC$;</li><li>$DMA$数据传输:收到$HLDA$信号将内存地址放置在地址总线上,设置控制总线读写命令控制信号,并向设备$DMA$发送应答信号$DACK$,设备收到$DACK$信号后会和内存完成一个机器字的数据交换;</li><li>传输控制:设备传输完一次数据后会继续重复第$1$步到第$5$步的工作,$DMAC$在每次传输时还需要负责维护内存地址和传输计数器,并撤除$HOLD$信号释放总线;</li><li>数据传输结束:$DMAC$会通过$INTR$信号线发送一个$EOP(End\enspace Of\enspace Process)$的$DMA$中断请求信号,告知$CPU$传输完成</li></ul></li><li><p>结束阶段($CPU$干预)</p><ul><li>$DMA$在两种情况下都进入结束阶段<ul><li>正常结束,一批数据传送完毕</li><li>非正常结束,$DMA$故障</li></ul></li><li>结束阶段$DMA$向主机发出中断请求</li><li>$CPU$执行中断服务程序<ul><li>查询$DMA$接口状态,根据状态进行不同处理</li></ul></li></ul></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机组成原理 </tag>
</tags>
</entry>
<entry>
<title>计算机组成原理笔记(五)</title>
<link href="/posts/2022/06/12/notes/Principles-of-Computer-Composition-5/"/>
<url>/posts/2022/06/12/notes/Principles-of-Computer-Composition-5/</url>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="六、中央处理器"><a href="#六、中央处理器" class="headerlink" title="六、中央处理器"></a>六、中央处理器</h2><h3 id="1-CPU-的组成与功能"><a href="#1-CPU-的组成与功能" class="headerlink" title="1.$CPU$的组成与功能"></a>1.$CPU$的组成与功能</h3><h4 id="1-基本组成"><a href="#1-基本组成" class="headerlink" title="(1)基本组成"></a>(1)基本组成</h4><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-CPU%E4%B8%BB%E8%A6%81%E7%BB%84%E6%88%90.png!blogimg" class="" width="400" title="CPU主要组成"><ul><li>运算器:数据加工</li><li>控制器:程序执行/指令执行<ul><li>取指令:$Mem[PC++]\rightarrow IR$</li><li>执行指令:指令字$\rightarrow$控制信号序列$\rightarrow$数据通路</li></ul></li></ul><h4 id="2-主要寄存器"><a href="#2-主要寄存器" class="headerlink" title="(2)主要寄存器"></a>(2)主要寄存器</h4><ul><li><p>程序计数器:$PC (Program\enspace Counter)$,如$X86:EIP\enspace MIPS:PC$</p></li><li><p>指令寄存器:$IR (Instruction Register)$,可选</p></li><li><p>地址寄存器:$AR(Address\enspace Register)$,如$MAR$,可选</p></li><li><p>数据缓冲寄存器:$DR(Data\enspace Register)$,如$MDR$,可选</p></li><li><p>累加寄存器:$AC(Accumulate\enspace Count)$,可选</p></li><li><p>程序状态字:$PSW (Program\enspace Status\enspace Word)$,如$X86:\enspace EFLAGS$,而$MIPS$无</p></li></ul><h4 id="3-主要功能"><a href="#3-主要功能" class="headerlink" title="(3)主要功能"></a>(3)主要功能</h4><ul><li>取指令并执行指令的部件——$CPU$<ul><li>运算器功能<ul><li>数据加工: 算术/逻辑运算</li></ul></li><li>控制器功能<ul><li>程序控制: 程序中指令执行顺序控制</li><li>操作控制: 将机器指令翻译成执行部件所需操作控制信号</li><li>时序控制: 控制操作信号的产生时间、持续时间</li><li>异常控制: 异常处理,外设交互</li></ul></li></ul></li></ul><h4 id="4-操作控制器"><a href="#4-操作控制器" class="headerlink" title="(4)操作控制器"></a>(4)操作控制器</h4><ul><li><p>取指令,将机器指令译码并生成执行部件控制信号序列;</p></li><li><p>建立正确的数据通路,从而完成指令的正确执行;</p></li><li><p>实现方法</p><ul><li><p>硬布线控制器 (时序逻辑型) (硬件实现)</p></li><li><p>微程序控制器 (存储程序型) (软件实现)</p></li></ul></li></ul><h3 id="2-数据通路"><a href="#2-数据通路" class="headerlink" title="2.数据通路"></a>2.数据通路</h3><h4 id="1-概述"><a href="#1-概述" class="headerlink" title="(1)概述"></a>(1)概述</h4><ul><li><p>数据通路——执行部件间传送信息的路径</p><ul><li>通路的建立由<strong>控制信号</strong>控制,受<strong>时钟驱动</strong>;</li><li>不同指令、同一指令在执行的不同阶段的数据通路不同;</li><li>分为共享通路(总线结构)和专用通路</li></ul></li><li><p>数据通路抽象模型</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E6%95%B0%E6%8D%AE%E9%80%9A%E8%B7%AF%E6%8A%BD%E8%B1%A1%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="数据通路抽象模型"></li><li><p>数据通路与时钟周期</p><ul><li><p>在不同的数据通路抽象模型中组合逻辑产生的延迟不同</p></li><li><p>在$D$触发器定时模型中,设如下定义:</p><ul><li>时钟触发前,输入必须稳定一段时间,称为建立时间$(Setup\enspace Time)$;</li><li>时钟触发后输入必须稳定一段时间,称为保持时间$(Hold\enspace Time)$;</li><li>时钟触发到输出稳定的时间称为触发器延迟$Clk_to_Q$</li></ul></li><li><p>假设在如下寄存器传输模型中,将最长的传输路径称为<strong>最长关键传输路径</strong>,相应的有<strong>最短关键传输路径</strong>。</p><ul><li><p>数据通路最小时钟周期>Clk_to_Q+最长关键路径时延+$Setup\enspace Time$</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E6%97%B6%E9%92%9F%E5%91%A8%E6%9C%9F%E4%B8%8E%E6%95%B0%E6%8D%AE%E9%80%9A%E8%B7%AF1.png!blogimg" class="" width="400" title="时钟周期与数据通路1"></li><li><p>Clk_to_Q+最短关键传输路径时延>$Hold\enspace Time$</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E6%97%B6%E9%92%9F%E5%91%A8%E6%9C%9F%E4%B8%8E%E6%95%B0%E6%8D%AE%E9%80%9A%E8%B7%AF2.png!blogimg" class="" width="400" title="时钟周期与数据通路2"></li></ul></li></ul></li></ul><h4 id="2-不同总线结构的数据通路"><a href="#2-不同总线结构的数据通路" class="headerlink" title="(2)不同总线结构的数据通路"></a>(2)不同总线结构的数据通路</h4><ul><li><p>共享通路(总线型)</p><ul><li>主要部件都连接在公共总线上,各部件间通过总线进行数据传输</li><li>结构简单,实现容易,但并发性较差,需<strong>分时使用总线</strong>,效率低</li></ul></li><li><p>专用通路</p><ul><li>并发度高,性能佳,设计复杂,成本高</li><li>可以看作<strong>多总线</strong>结构</li></ul></li><li><p>不同总线结构的数据通路对比</p><p>尝试使用以下不同总线结构执行$ADD\enspace R_0,R_1$指令</p><ul><li><p>单总线结构:两个锁存器,$3$个时钟周期</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%8D%95%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84%E6%95%B0%E6%8D%AE%E9%80%9A%E8%B7%AF%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="单总线结构数据通路模型"></li><li><p>双总线结构:一个锁存器,$2$个时钟周期</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%8F%8C%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84%E6%95%B0%E6%8D%AE%E9%80%9A%E8%B7%AF%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="双总线结构数据通路模型"></li><li><p>三总线结构:无需锁存器,$1$个时钟周期</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E4%B8%89%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84%E6%95%B0%E6%8D%AE%E9%80%9A%E8%B7%AF%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="三总线结构数据通路模型"></li></ul></li></ul><h4 id="3-单总线结构-CPU-与数据通路"><a href="#3-单总线结构-CPU-与数据通路" class="headerlink" title="(3)单总线结构$CPU$与数据通路"></a>(3)单总线结构$CPU$与数据通路</h4><ul><li><p>单总线$CPU$实例</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%8D%95%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84CPU%E5%AE%9E%E4%BE%8B.png!blogimg" class="" width="400" title="单总线结构CPU实例"><ul><li>主要部件都连接在总线上</li><li>各部件间通过总线进行传输</li><li>控制信号说明</li></ul></li></ul><table><thead><tr><th align="center">控制信号</th><th align="center">作用信号</th></tr></thead><tbody><tr><td align="center">$IR_{out},PC_{out},\cdots,R1_{out}$</td><td align="center">控制三态门将寄存器值输出到总线(<u><strong>互斥</strong></u>)</td></tr><tr><td align="center">$IR_{in},PC_{in},\cdots,R1_{in}$</td><td align="center">控制寄存器使能端将总线数据锁存(时钟驱动)</td></tr><tr><td align="center">$+1\enspace ADD\enspace SUB$</td><td align="center">运算控制信号(<u><strong>互斥</strong></u>)</td></tr><tr><td align="center">$Write\enspace Read$</td><td align="center">内存读写控制信号(时钟驱动)</td></tr></tbody></table><ul><li><p>单总线$MIPS\enspace CPU$模型下不同指令的数据通路</p><ul><li><p>使用的$MIPS\enspace CPU$模型</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%8D%95%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84CPU%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="单总线结构CPU模型"></li><li><p>使用的$MIPS$指令功能</p></li></ul></li></ul><table><thead><tr><th align="center">#</th><th align="center">指令</th><th align="center">指令功能</th></tr></thead><tbody><tr><td align="center">$1$</td><td align="center">$lw\enspace rt,imm(rs)$</td><td align="center">$Mem[R[rs]+imm]\rightarrow R[rt]$</td></tr><tr><td align="center">$2$</td><td align="center">$sw\enspace rt,imm(rs)$</td><td align="center">$R[rt]\rightarrow Mem[R[rs]+imm]$</td></tr><tr><td align="center">$3$</td><td align="center">$beq\enspace rs,rt,imm$</td><td align="center">$if(R[rs]==R[rt])\quad PC+4+IMM<<2\rightarrow PC$</td></tr><tr><td align="center">$4$</td><td align="center">$add\enspace rd,rs,rt$</td><td align="center">$R[rs]+R[rt]\rightarrow R[rd]$</td></tr><tr><td align="center">$5$</td><td align="center">$addi\enspace rt,rs,imm$</td><td align="center">$R[rs]+imm\rightarrow R[rt]$</td></tr></tbody></table><ul><li><p>指令周期方框图(数据流)</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E6%96%B9%E6%A1%86%E5%9B%BE%EF%BC%88%E6%95%B0%E6%8D%AE%E6%B5%81%EF%BC%89.png!blogimg" class="" width="400" title="指令周期方框图(数据流)"></li></ul><h3 id="3-指令周期"><a href="#3-指令周期" class="headerlink" title="3.指令周期"></a>3.指令周期</h3><ul><li><p>指令执行的流程</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E6%8C%87%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B.png!blogimg" class="" width="400" title="指令执行流程"><ul><li>如图所示,取指令、执行指令反复循环,其中白色圆圈的步骤可选</li><li>指令功能、寻址方式不同,数据通路、 执行时间不同,如何安排时序?</li></ul></li><li><p>指令周期、机器周期和时钟周期</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E3%80%81%E6%9C%BA%E5%99%A8%E5%91%A8%E6%9C%9F%E5%92%8C%E6%97%B6%E9%92%9F%E5%91%A8%E6%9C%9F.png!blogimg" class="" width="400" title="指令周期、机器周期和时钟周期"><ul><li>时钟周期$=$节拍脉冲$=$震荡周期——能完成一次微操作</li><li>机器周期$=CPU$周期:从主存读出一条指令的<u>最短时间</u>——可完成复杂操作</li><li>指令周期:从主存取一条指令并执行指令的时间<ul><li>指令周期包含$0\sim n$个机器周期,机器周期包含$k$个节拍</li></ul></li></ul></li><li><p>指令控制同步</p><p>不同指令的功能不同,所需的机器周期数不同,因此有不同的时间控制方式</p><ul><li>定长指令周期<ul><li>一条指令执行所需的机器周期数<strong>固定</strong>,每个机器周期的时钟周期数<strong>固定</strong></li><li>按机器周期同步,如$MIPS$单周期,只需一个机器周期即可完成指令</li></ul></li><li>变长指令周期<ul><li>一条指令执行所需的机器周期数<strong>可变</strong>,每个机器周期的时钟周期数<strong>可变</strong></li><li>按时钟周期同步,如$MIPS$多周期</li></ul></li></ul></li></ul><h3 id="4-时序与控制"><a href="#4-时序与控制" class="headerlink" title="4.时序与控制"></a>4.时序与控制</h3><h4 id="1-时序发生器"><a href="#1-时序发生器" class="headerlink" title="(1)时序发生器"></a>(1)时序发生器</h4><ul><li><strong>循环</strong>产生周期电位、节拍电位,供控制器对信号进行时间调制</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E6%97%B6%E5%BA%8F%E4%BA%A7%E7%94%9F%E5%99%A8%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="时序产生器模型"><h4 id="2-早期三级时序系统与现代时序系统"><a href="#2-早期三级时序系统与现代时序系统" class="headerlink" title="(2)早期三级时序系统与现代时序系统"></a>(2)早期三级时序系统与现代时序系统</h4><ul><li>早期三级时序系统包括状态周期、节拍电位、节拍脉冲三级时序</li><li>现代时序系统不再区分各级时序,定时信号就是基本时钟信号</li></ul><h4 id="3-定长指令周期三级时序(同步控制)"><a href="#3-定长指令周期三级时序(同步控制)" class="headerlink" title="(3)定长指令周期三级时序(同步控制)"></a>(3)定长指令周期三级时序(同步控制)</h4><ul><li><p>特点</p><ul><li>机器周期数、节拍数<strong>固定</strong>,设计简单,效率较低</li><li>时序发生器中<strong>不需要输入指令译码和反馈信号</strong></li><li>下跳沿改变状态机,上跳沿改变寄存器锁存</li></ul></li><li><p>指令周期、机器周期与时钟周期的关系</p><ul><li>使用状态周期电位(电平信号)表示当前处于什么机器周期</li><li>使用节拍电位表示处于机器周期的哪个节拍</li><li>使得时钟脉冲$\rightarrow$机器周期电位和节拍脉冲信号</li><li>使得硬布线控制器$\rightarrow$组合逻辑电路</li><li>如:$MemRead=M_{IF}\cdot(T_2+T_3)+Load\cdot M_{EX}\cdot(T_2+T_3)$</li><li>精准控制信号的<u><strong>产生时间与持续时间</strong></u></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%AE%9A%E9%95%BF%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E7%9A%84%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E5%9B%BE.png!blogimg" class="" width="400" title="定长指令周期的指令周期图"></li><li><p>设计时序发生器</p><ul><li><p>首先实现状态机</p><ul><li>根据现态和次态作出真值表得到$FSM$<u><strong>状态机组合逻辑</strong></u></li></ul></li><li><p>采用计数器逻辑,循环计数</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%AE%9A%E9%95%BF%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E7%8A%B6%E6%80%81%E6%9C%BA%E7%9A%84%E4%BA%A7%E7%94%9F.png!blogimg" class="" width="400" title="定长指令周期状态机的产生"></li><li><p>根据现态和各周期电位、节拍电位信号输出得到<u><strong>时序发生器组合逻辑</strong></u></p></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%AE%9A%E9%95%BF%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E6%97%B6%E5%BA%8F%E4%BA%A7%E7%94%9F%E5%99%A8%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="定长指令周期时序产生器模型"></li></ul><h4 id="4-变长指令周期三级时序(同步控制)"><a href="#4-变长指令周期三级时序(同步控制)" class="headerlink" title="(4)变长指令周期三级时序(同步控制)"></a>(4)变长指令周期三级时序(同步控制)</h4><ul><li><p>特点</p><ul><li>机器周期数<strong>可变</strong>、节拍数<strong>可变</strong>,无周期浪费,更加灵活</li><li><strong>根据指令类型</strong>决定完成取指令周期后跳转到哪个指令的对应周期</li></ul></li><li><p>指令周期、机器周期与时钟周期的关系</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%8F%98%E9%95%BF%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E7%9A%84%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E5%9B%BE.png!blogimg" class="" width="400" title="变长指令周期的指令周期图"></li><li><p>设计时序发生器</p><ul><li><p>首先实现状态机</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%8F%98%E9%95%BF%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E7%8A%B6%E6%80%81%E6%9C%BA%E7%9A%84%E4%BA%A7%E7%94%9F.png!blogimg" class="" width="400" title="变长指令周期状态机的产生"></li><li><p>时序发生器</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%8F%98%E9%95%BF%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E6%97%B6%E5%BA%8F%E4%BA%A7%E7%94%9F%E5%99%A8%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="变长指令周期时序产生器模型"></li></ul></li></ul><h3 id="5-硬布线控制器"><a href="#5-硬布线控制器" class="headerlink" title="5.硬布线控制器"></a>5.硬布线控制器</h3><h4 id="1-基本原理"><a href="#1-基本原理" class="headerlink" title="(1)基本原理"></a>(1)基本原理</h4><ul><li>基本原理图<ul><li>控制器:产生<u><strong>控制信号序列</strong></u>的逻辑电路——将机器指令字翻译为控制器信号序列</li><li>输入信号:指令译码,时钟,反馈信号</li><li>输出信号:功能部件控制信号序列</li></ul></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E6%8E%A7%E5%88%B6%E5%99%A8%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86%E5%9B%BE.png!blogimg" class="" width="400" title="控制器的基本原理图"><h4 id="2-三级时序硬布线控制器"><a href="#2-三级时序硬布线控制器" class="headerlink" title="(2)三级时序硬布线控制器"></a>(2)三级时序硬布线控制器</h4><ul><li><p>引入<u><strong>时序产生器</strong></u>的三级时序硬布线控制器</p><ul><li><p>将时序发生器从操作控制器中分离(分为定长指令周期和变长指令周期</p></li><li><p>将时序发生器输出的信号输入至硬布线控制器单元,产生控制信号序列</p></li><li><p>控制信号序列可以由译码信号、周期电位信号、节拍电平信号、反馈信号进行逻辑组合得到,其表达式为$C_n=\sum\limits_{m,i,k,j}(I_m\cdot M_i\cdot T_k\cdot B_j)$</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%BC%95%E5%85%A5%E6%97%B6%E5%BA%8F%E5%8F%91%E7%94%9F%E5%99%A8%E7%9A%84%E6%8E%A7%E5%88%B6%E5%99%A8%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86%E5%9B%BE.png!blogimg" class="" width="400" title="引入时序发生器的控制器基本原理图"></li></ul></li><li><p>设计步骤</p><ul><li>分析数据通路,画<u>指令周期流程图</u>,明确<u>各节拍控制信号</u></li><li>设计<u>时序发生器</u>:根据机器周期、节拍划分构建状态图</li><li>找出<strong>同一微操作控制信号</strong>产生条件</li><li>写出<strong>各微操作控制信号的逻辑表达式</strong></li><li>利用组合逻辑电路实现</li></ul></li></ul><h4 id="3-现代时序硬布线控制器"><a href="#3-现代时序硬布线控制器" class="headerlink" title="(3)现代时序硬布线控制器"></a>(3)现代时序硬布线控制器</h4><ul><li><p>现代时序硬布线控制器</p><ul><li><p>特点</p><ul><li><p>现代时序系统不需要设计时序发生器,将每个节拍都作为一个状态</p></li><li><p>状态转换图</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E7%8E%B0%E4%BB%A3%E6%97%B6%E5%BA%8F%E7%B3%BB%E7%BB%9F%E7%8A%B6%E6%80%81%E8%BD%AC%E6%8D%A2%E5%9B%BE.png!blogimg" class="" width="400" title="现代时序系统状态转换图"></li><li><p>$C_n=f(x)$,x表示次态</p></li><li><p>直接将状态寄存器的现态作为硬布线控制器组合逻辑电路的输入</p></li></ul></li><li><p>逻辑实现</p></li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E7%8E%B0%E4%BB%A3%E6%97%B6%E5%BA%8F%E6%8E%A7%E5%88%B6%E5%99%A8%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86%E5%9B%BE.png!blogimg" class="" width="400" title="现代时序控制器基本原理图"></li><li><p>设计步骤</p><ul><li>分析数据通路,画指令周期流程图,明确各节拍控制信号</li><li>绘制指令执行<strong>状态转换图</strong></li><li>构建<strong>状态转换表</strong></li><li>实现有限状态机组合逻辑</li><li>实现控制器组合逻辑电路</li></ul></li></ul><h4 id="4-硬布线控制器的特点"><a href="#4-硬布线控制器的特点" class="headerlink" title="(4)硬布线控制器的特点"></a>(4)硬布线控制器的特点</h4><ul><li>结构复杂,无规则</li><li>设计和调试困难</li><li>不可改变指令系统和指令功能</li><li>速度快</li></ul><h3 id="6-微程序控制器"><a href="#6-微程序控制器" class="headerlink" title="6.微程序控制器"></a>6.微程序控制器</h3><h4 id="1-工作原理"><a href="#1-工作原理" class="headerlink" title="(1)工作原理"></a>(1)工作原理</h4><ul><li>微程序是<strong>利用软件思想设计硬件</strong>的技术<ul><li>控制信号序列可以像程序一样存储起来<ul><li>控制信号序列分解为若干节拍</li><li>一个节拍的并发信号编成一条微指令</li><li>多个节拍对应多条微指令,形成一段微程序</li></ul></li><li>依序执行微程序即可生成控制信号序列<ul><li>指令取指执行$\rightarrow$微程序执行$\rightarrow$微指令执行$\rightarrow$生成控制信号</li><li>软时序:依次执行微指令,时间信号有先后顺序</li></ul></li></ul></li><li>存储技术和程序设计相结合,<strong>回避复杂时序逻辑设计</strong></li></ul><h4 id="2-微指令格式"><a href="#2-微指令格式" class="headerlink" title="(2)微指令格式"></a>(2)微指令格式</h4><ul><li><p>操作控制字段:存储操作控制信号</p><ul><li>每一位对应一个控制信号,也称微命令</li><li>可同时给出多个操作信号</li></ul></li><li><p>顺序控制字段:用于控制微程序的执行顺序</p><ul><li>判别字段为零,下一条微指令地址从下址字段获取</li><li>判别字段不为零,按约定规则生成下一条微指令地址</li></ul></li><li><p>格式特点</p><ul><li>一条微指令对应一个时钟周期</li><li>微指令操作控制字段的信号在当前时钟周期内有效,为$1$有效</li><li>指令需要多少时钟周期就包括多少微指令</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%BE%AE%E6%8C%87%E4%BB%A4%E6%A0%BC%E5%BC%8F.png!blogimg" class="" width="400" title="微指令格式"></li></ul><h4 id="3-微指令执行原理"><a href="#3-微指令执行原理" class="headerlink" title="(3)微指令执行原理"></a>(3)微指令执行原理</h4><ul><li>初始化:$μAR=0$ , $0$号地址为取指微程序入口</li><li>顺序执行取指微程序,最后一条微指令判别字段非零,地址跳转</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%BE%AE%E6%8C%87%E4%BB%A4%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86%E6%A1%86%E5%9B%BE.png!blogimg" class="" width="400" title="微指令执行原理框图"><ul><li><p>地址转移逻辑原理</p><ul><li>每个判别测试位对应一个微程序分支地址</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%9C%B0%E5%9D%80%E8%BD%AC%E7%A7%BB%E9%80%BB%E8%BE%91%E5%8E%9F%E7%90%86.png!blogimg" class="" width="400" title="地址转移逻辑原理"></li><li><p>主存与控制存储器</p></li></ul><table><thead><tr><th></th><th><strong>存放内容</strong></th><th><strong>存储容量</strong></th><th><strong>存储字长</strong></th><th><strong>存储速度</strong></th><th><strong>介质</strong></th></tr></thead><tbody><tr><td>主存</td><td>数据与程序</td><td>大</td><td>机器字长</td><td>慢</td><td>$RAM$</td></tr><tr><td>控制存储器</td><td>微程序</td><td>小</td><td>微指令长度</td><td>快</td><td>$ROM/RAM$</td></tr></tbody></table><h4 id="4-微程序设计"><a href="#4-微程序设计" class="headerlink" title="(4)微程序设计"></a>(4)微程序设计</h4><ul><li><p>设计原则</p><ul><li>有利于缩短微指令字长度</li><li>有利于减少控制存储器容量</li><li>有利于提高微程序执行速度</li><li>有利于对微指令进行修改</li><li>有利于提高微程序设计的灵活性</li></ul></li><li><p>微指令编码</p><ul><li><p>直接表示法</p><ul><li>简单直观,便于输出控制,但是微指令长度太长,控存容量大</li><li>直接表示$\rightarrow$编码表示——压缩互斥性微命令(最短为$18$位)</li><li>下址字段$\rightarrow$计数器法——$μAR++$,需要增加$P_{end}$判别位</li><li>水平型$\rightarrow$垂直型微指令——牺牲并行性</li></ul></li><li><p>编码表示法</p><ul><li><p>压缩互斥性微命令</p><ul><li>思考:如果互斥的输出控制信号$8$个,编码后长度多少?</li></ul><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%BE%AE%E6%8C%87%E4%BB%A4%E7%BC%96%E7%A0%81%E8%A1%A8%E7%A4%BA%E6%B3%95.png!blogimg" class="" width="400" title="微指令编码表示法"></li><li><p>计数器法</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E5%BE%AE%E6%8C%87%E4%BB%A4%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86%E6%A1%86%E5%9B%BE%EF%BC%88%E8%AE%A1%E6%95%B0%E5%99%A8%E6%B3%95%EF%BC%89.png!blogimg" class="" width="400" title="微指令执行原理框图(计数器法)"></li></ul></li></ul></li></ul><h4 id="5-硬布线与微程序对比"><a href="#5-硬布线与微程序对比" class="headerlink" title="(5)硬布线与微程序对比"></a>(5)硬布线与微程序对比</h4><ul><li>硬布线:同步逻辑、繁,快,贵,难改<ul><li>一条指令多个时钟周期、一个时钟周期一个状态、一个状态对应一组并发信号</li><li>适合$CISC$等功能较复杂的系列机$X86$、$IBM\enspace S/360$</li><li>可写控存方便修复出厂故障——$Intel\enspace Core\enspace 2$、$Intel\enspace Xeon$</li></ul></li><li>微程序:存储逻辑、简、慢、廉,易改<ul><li>控制流存储为微指令微程序,一个状态对应一条微指令</li><li>一条指令对应多条微指令、状态字等同与存储器地址</li><li>适合$RISC$计算机,如$MIPS$、$ARM$</li></ul></li></ul><h3 id="7-异常与中断处理"><a href="#7-异常与中断处理" class="headerlink" title="7.异常与中断处理"></a>7.异常与中断处理</h3><ul><li><p>中断的处理过程</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E4%B8%AD%E6%96%AD%E5%A4%84%E7%90%86%E6%A8%A1%E5%9E%8B.png!blogimg" class="" width="400" title="中断处理模型"><ul><li><p>关中断</p><p>临时禁止中断请求,是为了保障中断响应周期以及中断服务程序中保护现场操作的完整性;</p></li><li><p>保存断点</p><p>保存将来返回被中断程序的位置,对于已经执行完毕的指令其断点是下一条指令的位置(<strong>注意有可能不是顺序指令</strong>),对于缺页故障,段错等执行指令引起的故障异常,由于指令并没有执行,所以断点应该是异常指令的 PC 值。</p></li><li><p>中断识别</p><p>根据当前的中断请求识别出<strong>中断来源</strong>,也即发生了什么中断,并<strong>将对应中断的中断服务程序入口地址送程序计数器$PC$</strong></p></li></ul></li><li><p>中断异常处理的数据通路</p><img src="https://picbed.cloudchewie.com/blog/post/Principles-of-Computer-Composition-5/Chp6-%E4%B8%AD%E6%96%AD%E5%A4%84%E7%90%86%E6%95%B0%E6%8D%AE%E9%80%9A%E8%B7%AF.png!blogimg" class="" width="400" title="中断处理数据通路"><ul><li>为了支持中断添加的相关支持<ul><li>开关中断:增加$IE$寄存器</li><li>保存断点:增加$EPC$寄存器/堆栈</li><li>中断识别:增加中断控制逻辑</li><li>软件支持:$eret$指令支持</li></ul></li><li>添加的相关信号<ul><li>$EPC_{in}$,$EPC_{out}$</li><li>开中断$STI$,关中断$CLI$</li><li>$IntA_{out}$:将中断服务程序地址送入地址总线</li><li>$ClrInt$:清空正在响应的中断请求</li></ul></li><li>中断响应周期的操作与控制信号<ul><li>节拍$T_1$:$0\rightarrow IE$,$PC\rightarrow EPC$,关中断并保存断点<ul><li>控制信号:$CLI$,$PC_{out}$,$EPC_{in}$</li></ul></li><li>节拍$T_2$:中断程序入口$\rightarrow PC$,中断服务程序地址送入$PC$<ul><li>控制信号:$IntA_{out}$,$PC_{in}$</li></ul></li></ul></li><li>$eret$指令执行周期的操作与控制信号<ul><li>节拍$T_3$:$EPC\rightarrow PC$,$1\rightarrow IE$,恢复断点并开中断<ul><li>控制信号$EPC_{out}$,$PC_{in}$,$STI$,$ClrInt$</li></ul></li></ul></li></ul></li></ul>]]></content>
<categories>
<category> 专业核心 </category>
</categories>
<tags>
<tag> 课程笔记 </tag>
<tag> 计算机组成原理 </tag>
</tags>
</entry>
</search>