-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
518 lines (292 loc) · 556 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Anduin9527的乖离器</title>
<subtitle>正在进修摸鱼学导论</subtitle>
<link href="http://lapras.xyz/atom.xml" rel="self"/>
<link href="http://lapras.xyz/"/>
<updated>2022-11-07T08:23:07.567Z</updated>
<id>http://lapras.xyz/</id>
<author>
<name>Anduin9527</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>编译原理(二)--词法分析</title>
<link href="http://lapras.xyz/2022/11/07/5fc886fa.html"/>
<id>http://lapras.xyz/2022/11/07/5fc886fa.html</id>
<published>2022-11-07T07:58:36.494Z</published>
<updated>2022-11-07T08:23:07.567Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>嘿嘿嘿,boki酱可爱捏</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221107155740.jpg" alt="102545983_p0_master1200" style="zoom: 50%;" / loading="lazy"></p><span id="more"></span><h2 id="概述">概述</h2><h3 id="词法分析的任务">词法分析的任务</h3><p><strong>词法分析器</strong> 需要做的就是从左至右逐个字符地对源程序进行扫描,识别一个个单词符号,具体而言:</p><ol type="1"><li><p><strong>消除无用字符</strong>,对源程序文本进行处理,消除源程序文本中的注释、空格、换行符及其他一切对语法分析和代码生成无用的信息</p></li><li><p><strong>识别单词</strong>,扫描源程序的一个个字符,按照语言的词法规则,识别出各类有独立意义的单词</p></li><li><p>对识别出来的单词进行 <strong>内部编码</strong>。将长度不一、种类不同的单词用长度统一、格式规整、分类清晰的内部编码表示</p></li><li><p>建立各种 <strong>表格</strong>(如名字特征表、常数表等)</p></li></ol><p>编译程序实现词法分析时,可以进行单独一遍扫描,也可以和语法分析放在同一遍扫描中</p><h3 id="单词的分类与表示">单词的分类与表示</h3><p>通常在程序设计语言中,可以将单词分为以下几类</p><ol type="1"><li>关键字:如 begin, repeat, if,...</li><li>界限符:逗号、分号、括号和空白</li><li>运算符:+,-,*,/,...</li><li>常数:各种类型的常数</li><li>标识符一表示各种名字:如变量名、数组名和过程名</li></ol><p>对于单词的表示,通常要先对其进行 <strong>分类</strong> 然后进行 <strong>编码</strong>。通常将单词编码分为两部分:<strong>类别编码</strong> 和 <strong>单词自身编码</strong>,并以一个二元组的形式给出。</p><p>单词的类别编码有两种方案:</p><ol type="1"><li>一类一种:根据单词的五大种类进行划分,为每一类分配一个类型码</li><li>一字一种:即设计之初就定义下来,比如 <code>for</code> 就是 12 之类的</li></ol><p>关键字、运算符、界限符等专用符号在语言设计之初其属性就不会再发生变化,因此其 <strong>自身编码值</strong> 可以直接固定。而对于标识符和常量这种单词的自身值编码同样也有两种方案:</p><ol type="1"><li>标识符单独为一种,自身的值表示成按照机器字节划分的内部码;常数按照类型分种,常数的值则表示为标准的 2 进制数形式</li><li>引入符号表,将标识符和常量分别放入相应的表中,用表中的相对地址码作为单词的值</li></ol><h2 id="词法分析程序">词法分析程序</h2><p>词法分析程序分为手工编码实现方案和利用自动生成器两种,前者相对比较复杂、易出错但效率较高,生成的词法分析程序的代码量较少(GCC)。后者可快速成型,代码量少,但较难控制细节,调优比较难(Lex等)</p><h3 id="状态转移图">状态转移图</h3><p>转换图实际上是一个 <strong>有限方向图</strong>,图中结点代表 <strong>状态</strong>,用 <strong>圆圈</strong> 表示。状态之间用 <strong>有向边</strong> 连接,有向边上标记某个符号,其含义是某一状态下,如果当前的输入符号是有向边上标记的符号,则转换到另一状态或留在原状态</p><p><strong>转换图只能存在一个初态和至少一个终态(双圈表示)</strong></p><pre class="language-mermaid" data-language="mermaid"><code class="language-mermaid"><span class="token keyword">graph</span> LRid1<span class="token text string">((1))</span><span class="token inter-arrow-label"><span class="token arrow-head arrow operator">--</span><span class="token label property">a</span><span class="token arrow operator">--></span></span>id2<span class="token text string">((2))</span>id1 <span class="token inter-arrow-label"><span class="token arrow-head arrow operator">--</span><span class="token label property">b</span><span class="token arrow operator">--></span></span>id3<span class="token text string">((3))</span></code></pre><p>该状态转换图表示在状态 1 下读 a 转到状态 2,若在状态 1 下读入字符 b,则转到状态 3</p><p>同样的,一个状态转换图可以用于识别一定的字符串,例如 C 语言表视符的转换图:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929164545.png" alt="image-20220929164545827" style="zoom:80%;" / loading="lazy"></p><p>其中,S 为 <strong>初态</strong>,Z 为 <strong>终态</strong>。这个状态转换图识别(接受)标识符的过程:从初态 S 开始,若编译器扫描到了一个字母或下画线,则读入该字母或下画线,并转入状态 1;在状态 1 下,若编译器又扫描到了一个字母或下画线或数字,则仍然读进,并再次进入状态 1,重复这个过程,直到在状态 1 下发现编译器扫描到的符号不再是字母或下画线或数字时,进入状态 Z。状态 Z 是终态,它意味着到此已识别出一个 C 语言的标识符,识别过程 <strong>宣告终止</strong>。终态 Z 的右上角有一个 <strong>星号</strong>,这表示读进了一个不属于标识符的符号(如界限符、空格等),应把它退还给输入串,用于识别下一个单词</p><h3 id="左线性文法构造状态转移图">左线性文法构造状态转移图</h3><p>正规文法包含左线性文法和右线性文法。词法规则往往可以采用正规文法来构造,而状态转换图恰恰又可用于识别单词,因此它们之间实际存在“等价”关系。所以可以将正规文法转换为状态转换图</p><p>令文法 <span class="math inline">\({G}=({V}_, {V}_, {P}, {Z})\)</span> 是一个左线性文法, 并假设 <span class="math inline">\(|{V}_|={n}\)</span>, 则构造出的状态转换图共有 <span class="math inline">\({n}+1\)</span> 个状态, 其对应的状态转换图构造步骤如下 (其中 <span class="math inline">\(U, B \in V_N, a, c \in V_T\)</span> )</p><ol type="1"><li>将每个 <strong>非终结符号</strong> 设置成一个对应的 <strong>状态</strong>, 文法的 <strong>开始符号</strong> <span class="math inline">\(Z\)</span> 所对应的状态为 <strong>终止状态</strong></li><li>在图中增加一个结点 <span class="math inline">\(S\)</span> 作为 <strong>初始状态</strong>,<span class="math inline">\(S\)</span> 并非文法中的符号</li><li>对于 <span class="math inline">\({G}\)</span> 中形如 <span class="math inline">\(U arrow a\)</span> 的规则,从初始状态 <span class="math inline">\(S\)</span> 向状态 <span class="math inline">\(U\)</span> 引一条箭弧,并标记为 <span class="math inline">\(a\)</span></li><li>对于 <span class="math inline">\({G}\)</span> 中形如 <span class="math inline">\({U} arrow {Bc}\)</span> 的规则,从状态 <span class="math inline">\({B}\)</span> 向状态 <span class="math inline">\({U}\)</span> 引一条箭弧,并标记为 <span class="math inline">\({c}\)</span></li></ol><p>栗子:设有左线性文法 <span class="math inline">\(G=(V_N, V_T, P, Z), V_N=\{Z, A, B\}, V_T=\{0,1\}\)</span>,其中 <span class="math inline">\(P\)</span> :</p><p><span class="math display">\[{Z} \rightarrow {A} 0|{~B} 1 \quad {~A} \rightarrow {Z} 1| 1 \quad {~B} \rightarrow {Z} 0 \mid 0\]</span></p><p>则:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929165443.png" alt="image-20220929165443620" style="zoom:80%;" / loading="lazy"></p><p>首先明确一点,左线性文法构造状态转移图是根据规则反向(自底向上)进行推理的</p><p>不难发现,首先从 <span class="math inline">\(S\)</span> 出发,利用右部只有终结符的规则 <span class="math inline">\(B \to 0,\ A \to 1\)</span> 得到状态 <span class="math inline">\(A,\ B\)</span>,然后用 <span class="math inline">\(Z \to A0|B1\)</span> 获得 $A ^0 Z, B ^1 Z $。最后根据 <span class="math inline">\(A \to Z1, \ B\to Z0\)</span>,构造剩下两条由 <span class="math inline">\(Z\)</span> 出发的路径</p><p>那么这个具体的用处是什么呢?答曰:识别某个字符串 <span class="math inline">\(x\)</span> 是否为该文法下的合法句子,如果其从初始状态 <span class="math inline">\(S\)</span> 出发,与 <span class="math inline">\(x\)</span> 余留部分中最左字符相匹配的原则,游历状态转换图,直到 <span class="math inline">\(x\)</span> 读入最后一个符号为止。如果这时恰好到达状态 <span class="math inline">\(Z\)</span>(即文法的开始符号),则 x 是该文法所产生的句子(单词)之一,否则不是</p><p>比如识别字符串 <span class="math inline">\(101001\)</span>,其路径如下</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929170235.png" alt="image-20220929170235324" style="zoom:80%;" / loading="lazy"></p><p>通过路径还可以还原其语法树:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929170323.png" alt="image-20220929170323712" style="zoom:80%;" / loading="lazy"></p><p>可见,这颗树是向左生长的(左线性)。事实上,该文法最终生成的语言为 <span class="math inline">\(\{ 01,10 \}^+\)</span></p><h3 id="右线性文法构造状态转移图">右线性文法构造状态转移图</h3><p>令文法 <span class="math inline">\({G}=({V}_, {V}_, {P}, {Z})\)</span> 是一个右线性文法, 并假设 <span class="math inline">\(|{V}_|={n}\)</span>, 则构造出的状态转换图共有 <span class="math inline">\({n}+1\)</span> 个状态, 其对应的状态转换图构造步骤如下 (其中 <span class="math inline">\(U, B \in V_N, a, c \in V_T\)</span> )</p><ol type="1"><li>将每个 <strong>非终结符号</strong> 设置成一个对应的 <strong>状态</strong>, 文法的 <strong>开始符号</strong> <span class="math inline">\(Z\)</span> 所对应的状态为 <strong>终止状态</strong></li><li>在图中增加一个结点 <span class="math inline">\(S\)</span> 作为 <strong>初始状态</strong>,<span class="math inline">\(S\)</span> 并非文法中的符号</li><li>对于 <span class="math inline">\({G}\)</span> 中形如 <span class="math inline">\(U arrow a\)</span> 的规则,从状态 <span class="math inline">\(U\)</span> 向终止状态 <span class="math inline">\(Z\)</span> 引一条箭弧,并标记为 <span class="math inline">\(a\)</span></li><li>对于 <span class="math inline">\({G}\)</span> 中形如 <span class="math inline">\({U} arrow {cB}\)</span> 的规则,从状态 <span class="math inline">\({U}\)</span> 向状态 <span class="math inline">\({B}\)</span> 引一条箭弧,并标记为 <span class="math inline">\({c}\)</span></li></ol><p>不难发现,右线性文法构造时,是顺序(自顶向下)推理的</p><p>栗子,设有右线性文法 <span class="math inline">\({G}[S]=({V}_{N}, {V}_{T}, {P}, S), {V}_{N}=\{S, {A}, {B}, {C}\}, {V}_{T}=\{0,1\}\)</span>, 其中 <span class="math inline">\({P}\)</span> : <span class="math display">\[{S} \rightarrow 1 {~A}|0 {~B} \quad {~A} \rightarrow 0 {C}| 0 \quad {~B} \rightarrow 1 {C}|1 \quad {C} \rightarrow 0 {~B}| 1 {~A}\]</span> 则</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929173424.png" alt="image-20220929173424164" style="zoom:80%;" / loading="lazy"></p><p>事实上,该文法最终生成的语言同为 <span class="math inline">\(\{ 01,10 \}^+\)</span>,可称其与上文左线性文法为 <strong>左右线性文法等价,不同状态转移图等价,正规文法和状态转移图等价</strong>。换言之,状态转移图,等价左右线性文法是知一求二的关系</p><h2 id="自动词法分析程序">自动词法分析程序</h2><h3 id="正规表达式">正规表达式</h3><p>正规表达式是一种通过符号组成的式子来表达语言(句子集合)的方式。它简单、直观,与 <strong>集合</strong> 的表现形式更为相近,因此应用起来也更为方便</p><p>每一类程序设计语言都有它自己的 <strong>字符集</strong> <span class="math inline">\(\Sigma\)</span>,语言中每一个单词可以是 <span class="math inline">\(\Sigma\)</span> 的单个有意义的字符(如运算符、分隔符等),也可以是 <span class="math inline">\(\Sigma\)</span> 上的字符按一定方式组成的有意义的字符串(如常数、保留字、标识符及关系运算符等)。如果我们把每类单词均视为一种“语言”,那么 <strong>每一类单词都可用一个正规表达式</strong> 来描述。正规表达式表示的“语言”叫作 <strong>正规集</strong></p><p><strong>正则表达式和正规集</strong>:</p><ol type="1"><li>定义 <span class="math inline">\(\varepsilon\)</span> 和 <span class="math inline">\(\varnothing\)</span> 是 <span class="math inline">\(\Sigma\)</span> 上的正规表达式,它们所表示的正规集分别为 <span class="math inline">\(\{\varepsilon\}\)</span> 和 <span class="math inline">\(\varnothing\)</span></li><li>对于每一个 <span class="math inline">\(a \in \Sigma\)</span>,<span class="math inline">\(a\)</span> 即为 <span class="math inline">\(\Sigma\)</span> 上的正规表达式,定义它表示的正规集为 <span class="math inline">\(\{a\}\)</span></li><li>反之,如果 <span class="math inline">\(\Sigma\)</span> 上的某些集合 <strong>不能用正规表达式表示</strong>,则该集合不是正规集</li></ol><p><strong>正规表达式和正规集的运算:</strong></p><p>如果 <span class="math inline">\(e_1\)</span> 和 <span class="math inline">\(e_2\)</span> 是 <span class="math inline">\(\Sigma\)</span> 上的正规表达式, 定义它们所表示的正规集分别为 <span class="math inline">\(L(e_1)\)</span> 和 <span class="math inline">\(L(e_2)\)</span>, 则:</p><ol type="1"><li><span class="math inline">\(e_1 \mid e_2\)</span> 是正规表达式,<span class="math inline">\(\mid\)</span> 表示选择,其相应正规集为 <span class="math inline">\(L(e_1 \mid e_2)=L(e_1) \cup L(e_2)\)</span></li><li><span class="math inline">\(e_1 \cdot e_2\)</span> 是正规表达式,<span class="math inline">\(\cdot\)</span> 表示连接 其相应正规集为 <span class="math inline">\(L(e_1 \cdot e_2)=L(e_1) L(e_2)\)</span>,通常 <strong>省略</strong> <span class="math inline">\(\cdot\)</span></li><li><span class="math inline">\(({e}_1)^*\)</span> 是正规表达式,<span class="math inline">\(*\)</span> 表示克林闭包,其相应正规集为 <span class="math inline">\({L}(({e}_1)^*)=({L}({e}_1))^*\)</span></li><li><strong>有限次</strong> 使用上述步骤定义的表达式才是 <span class="math inline">\(\Sigma\)</span> 上的正规表达式。仅由这些正规表达式所表示的符号串集合才是 <span class="math inline">\(\Sigma\)</span> 上的正规集</li><li>这三个运算符的运算优先级为 <span class="math inline">\(*,\ \cdot,\ \mid\)</span></li><li>除了这三个运算符之外,还可以使用 <strong>圆括号</strong> 改变运算顺序</li></ol><blockquote><p>栗子:<span class="math inline">\(\Sigma = \{a,b\}\)</span>,求正规表达式对应的正规集</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221107160511.png" alt="image-20221107160511115" style="zoom: 80%;" / loading="lazy"></p></blockquote><blockquote><p>栗子 2:</p><ol type="1"><li>十进制整数的 RE:<span class="math inline">\((1\mid ...\mid 9)(0\mid ...\mid 9)^*\mid 0\)</span></li><li>C 语言中八进制整数的 RE:<span class="math inline">\(0(1\mid ...\mid 7)(0\mid...\mid 7)^*\)</span></li><li>C 语言中十六进制整数的 RE:<span class="math inline">\(0x(1\mid ...\mid F)(0\mid ...\mid F)\)</span></li></ol></blockquote><p><strong>正规表达式的等价</strong>:若两个正规式所对应的 <strong>正规集</strong> 相同,则认为两者等价</p><blockquote><p>栗子 3: <span class="math inline">\(e_1=b(a b)^* \quad e_2=(b a)^* b\)</span> <span class="math display">\[\begin{aligned}&L(e_1)=L(b(a b)^*)=L(b)(L(a b))^*=\{b\}\{a b\}^*=\{b, b a b, b a b a b, \ldots\} \\&L(e_2)=L((b a)^* b)=(L(b a))^* L(b)=\{b a\}^*\{b\}=\{b, b a b, babab, , \ldots\}\end{aligned}\]</span> 所以 <span class="math inline">\(e_1 = e_2\)</span></p></blockquote><p><strong>正规表达式的性质</strong>:</p><ol type="1"><li>交换律:<span class="math inline">\(e_1 \vert e_2 = e_2\vert e_1\)</span></li><li>加法结合律:<span class="math inline">\(e_1 \vert (e_2\vert e_3) = (e_1 \vert e_2)\vert e_3\)</span></li><li>乘法结合律:<span class="math inline">\(e_1 (e_2 e_3) = (e_1 e_2) e_3\)</span></li><li>分配律:<span class="math inline">\(e_1 (e_2\vert e_3) = e_1e_2 \vert e_1e_3\)</span></li><li>空串连接:<span class="math inline">\(\varepsilon e_1 = e_1 \varepsilon = e_1\)</span></li><li>空集合积:<span class="math inline">\(\varnothing e_1= e_1 \varnothing =\varnothing\)</span></li><li>循环:<span class="math inline">\((e^*)^* = e^*\)</span></li><li>循环:<span class="math inline">\((\varepsilon \vert e)^* = e^*\)</span></li></ol><p><strong>正规定义</strong>:给给定的正规表达式起一个别名,比如可以用 <span class="math inline">\(hex \to 0x(1\mid ...\mid F)(0\mid ...\mid F)\)</span> 进行 16 进制数的正则定义,记为 <span class="math inline">\(hex\)</span></p><h3 id="有穷自动机">有穷自动机</h3><p>如前所述,使用 <strong>正规文法</strong> 或 <strong>正规表达式</strong> 可以定义语言的词法结构。在 <strong>手动编写方式</strong> 中,我们将正规文法转换成状 <strong>态转换图</strong>,根据状态转换图可以较为方便地编写出词法分析程序。对于计算机而言,状态转换图的描述方式是不易理解的,也不适合用于 <strong>自动生成词法分析程序</strong>。因此需要引入状态转换图的 <strong>形式化描述工具</strong>——<strong>有穷自动机</strong>,从识别语言的角度出发,确定某种模型来判断一个符号串是否是给定语言的句子</p><p>有穷自动机(Finite Automata,FA)也被称为有穷状态自动机或有穷状态系统,它是一种数学模型,这种模型对应的系统 <strong>具有有穷数目的内部状态</strong>,系统的状态概括了对过去输入的处理情况。系统根据当前所处的状态和面临的输入就可以决定后续行为。每当系统处理完当前的输入,系统的内部状态也会发生改变。有穷自动机分为确定的有穷自动机(Deterministic Finite Automata,DFA)和非确定的有穷自动机(Non-Deterministic Finite Automata,NFA),下面分别给出它们的形式化定义。</p><p>FA 定义的语言:给定输入串 <span class="math inline">\(x\)</span>, 如果存在一个对应于串 <span class="math inline">\(x\)</span> 的从初始状态到某个终止状态的转换序列,则称串 <span class="math inline">\(x\)</span> 被该 FA 接收由一个有穷自动机 <span class="math inline">\(M\)</span> 接收的 <strong>所有串构成的集合</strong> 称为是该 FA 定义(或接收)的 <strong>语言</strong>,记为 <span class="math inline">\(L(M)\)</span></p><p>FA 的最长子串匹配原则(贪婪原则):当输入串的多个前缀与一个或多个模式匹配时,总是选择最长的前缀进行匹配</p><h3 id="确定的有穷自动机">确定的有穷自动机</h3><p>一个确定的有穷自动机(DFA)由一个五元组 <span class="math inline">\(M\)</span> 定义, 即 <span class="math inline">\(M=({K}, {V}_, M, S, Z)\)</span></p><ul><li><p><span class="math inline">\({K}\)</span> 是状态有穷的非空集合,<span class="math inline">\({K}\)</span> 中每一个元素是一个状态</p></li><li><p><span class="math inline">\({V}_\)</span> 是一个有穷输入字母表, <span class="math inline">\({V}_\)</span> 中的每一个元素称为输入字符</p></li><li><p><span class="math inline">\(M\)</span> 是 <span class="math inline">\(K \times V_T\)</span> 到 <span class="math inline">\(K\)</span> 的单值映射(函数),即 <span class="math inline">\(M(q, a)= p, q, p \in K, a \in V_T\)</span> ,它表示:当前状态为 <span class="math inline">\({q}\)</span>,输入字符为 <span class="math inline">\({a}\)</span> 时, 将转到下一状态 <span class="math inline">\({p}\)</span>,<span class="math inline">\({p}\)</span> 是 <span class="math inline">\({q}\)</span> 的一个后继状态。<strong>由于映射是单值, 所以称确定的有穷自动机</strong></p></li><li><p><span class="math inline">\(S\)</span> 为初始状态, 是唯一初态, <span class="math inline">\(S \in {K}\)</span></p></li><li><p><span class="math inline">\({Z}\)</span> 是终止状态集合,<span class="math inline">\({Z}\)</span> 是 <span class="math inline">\({K}\)</span> 的子集</p></li></ul><p><strong>状态转移图</strong>:所以,我们可以通过这个五元组绘制出 <strong>DFA</strong> 的状态转移图。一个 <strong>DFA</strong> 可唯一表示一张确定的状态转换图。假定一 <strong>DFA</strong> 有 <span class="math inline">\(m\)</span> 个状态和 <span class="math inline">\(n\)</span> 个输入字符,则它的状态转换图含有 <span class="math inline">\(m\)</span> 个状态,每个结点最多有 <span class="math inline">\(n\)</span> 条箭弧和别的状态相连接,每条箭弧用 <span class="math inline">\(V_T\)</span> 中的一个输入字符标记,整个图含有唯一的初态和若干个终态。</p><p><strong>状态转移矩阵</strong>:一个 <strong>DFA</strong> 还可以用一个状态转换矩阵来表示,矩阵的 <strong>行表示状态</strong>,<strong>列表示输入字符</strong>,矩阵元素表示映射 <span class="math inline">\(M(q,a)=p\)</span></p><p><strong>DFA 输入符号串</strong>:定义如下:</p><ol type="1"><li><p><span class="math inline">\(M({q}, \varepsilon)={q}, {q} \in {K}\)</span>,一个状态输入空符号串回到自身</p></li><li><p><span class="math inline">\(M(q, a t)=M(M(q, a), t)=M(p, t)=\cdots\)</span>, 其中 <span class="math inline">\(a \in V_T, t \in V_T^*\)</span></p><p>当状态为 <span class="math inline">\(q\)</span>,输入字符串为 <span class="math inline">\(at\)</span> 时,利用映射 <span class="math inline">\(M({q}, {a})\)</span> 得到状态 <span class="math inline">\({p}\)</span>,然后利用映射 <span class="math inline">\(M({p}, {t})\)</span>,如此重复。如果对某一字符串 <span class="math inline">\(x\)</span>,有 <span class="math inline">\(M(S, x)=r\)</span>,而 <span class="math inline">\(r \in Z\)</span>,则称字符串 <span class="math inline">\(x\)</span> 被 <span class="math inline">\((DFA)M\)</span> <strong>接受</strong></p></li></ol><p><strong>DFA 接受集</strong>:将可被接受的字符串全体称为自动机 <span class="math inline">\(M\)</span> 的 <strong>接受集</strong> 或 <strong>所接受的语言</strong>,记作 <span class="math inline">\(L(M)\)</span></p><blockquote><p>栗子:(DFA) <span class="math inline">\(M=(\{0,1,2,3\},\{a, b\}, M, 0,\{3\})\)</span>,其中 <span class="math inline">\({K}=\{0,1,2,3\}\)</span>,<span class="math inline">\({V}_=\{a, b\}\)</span></p><p>对于输入字符串 <span class="math inline">\(a b b\)</span>, 因为从初始状态 0 出发, 有 <span class="math display">\[\mathrm{M}(0, \mathrm{a})= 1 \quad \mathrm{M}(1, \mathrm{~b})= 2 \quad \mathrm{M}(2, \mathrm{~b})= 3\]</span> 当输入完最后一个字符 <span class="math inline">\({b}\)</span> 时, 到达了终止状态 3 , 所以字符串 <span class="math inline">\(a b b\)</span> 能被此 DFA 所接受 (识别)。</p><p>其状态转移图:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221010132005.png" alt="image-20221010132005767" style="zoom:90%;" / loading="lazy"></p><p>其状态转移矩阵:</p><table><thead><tr class="header"><th>状态</th><th>a</th><th>b</th></tr></thead><tbody><tr class="odd"><td>0</td><td>1</td><td>2</td></tr><tr class="even"><td>1</td><td>3</td><td>2</td></tr><tr class="odd"><td>2</td><td>1</td><td>3</td></tr><tr class="even"><td>3</td><td>3</td><td>3</td></tr></tbody></table><p>显然,该自动机所接受的语言 <span class="math inline">\(L(M)\)</span> 为 <span class="math inline">\(\{a,b\}^+\)</span> 且至少含有相继两个 <span class="math inline">\(a\)</span> 或 <span class="math inline">\(b\)</span></p></blockquote><h3 id="非确定的有穷自动机">非确定的有穷自动机</h3><p>非确定的有穷自动机(NFA)与确定的有穷自动机的唯一区别就在于映射 <span class="math inline">\(M\)</span></p><p><span class="math inline">\(M\)</span> 是 <span class="math inline">\(K \times V_T\)</span> 到 <span class="math inline">\({K}\)</span> 的 <strong>幂集</strong>(所有子集的集合)上的映射,即 <span class="math inline">\(\{K \times V_T arrow 2^K\}\)</span></p><p><span class="math inline">\(M(q, a)=\{p_1, p_2, \cdots, p_n\} \in 2^K, {q} \in K, a \in V_T\)</span> ,表示:当前状态为 <span class="math inline">\({q}\)</span>,输入字符为 <span class="math inline">\({a}\)</span> 时,映射 <span class="math inline">\(M\)</span> 将产生一个状态集合 <span class="math inline">\(\{p_1, p_2, \cdots, p_n\}\)</span> (可能是空集),而不是单个状态,所以 <strong>称非确定的有穷自动机</strong></p><p><strong>NFA 输入符号串</strong>:</p><ol type="1"><li><span class="math inline">\(M(q, \varepsilon)=\{q\}, q \in K\)</span></li><li></li></ol><p><span class="math display">\[\begin{aligned}\mathrm{M}(\mathrm{q}, \mathrm{at})&=\mathrm{M}(\mathrm{M}(\mathrm{q}, \mathrm{a}), \mathrm{t})\\&=\mathrm{M}(\{\mathrm{p}_1, \mathrm{p}_2, \cdots, \mathrm{p}_{\mathrm{n}}\}, \mathrm{t}) \\&=\cup \mathrm{M}(\mathrm{p}_{\mathrm{i}}, \mathrm{t})\end{aligned}\]</span> 其中, <span class="math inline">\({i}\)</span> 从 1 变到 <span class="math inline">\({n}, {p}_i \in M({q}, {a}), {a} \in {V}_T, {t} \in {V}_T^*\)</span> ,如此继续。对于 <span class="math inline">\({V}_{ }^*\)</span> 上的字符串 <span class="math inline">\({x}\)</span>, 令 <span class="math inline">\(S_0 \in S\)</span>,若集合 <span class="math inline">\(M(S_0, {x})\)</span> 含有属于终态集 <span class="math inline">\({Z}\)</span> 的状态,<strong>或者至少存在一条从某一个初态结点到某一个终态结点的路径</strong>,且这条路径上所有箭弧的标记字符连接起来的字符串等于 <span class="math inline">\(x\)</span>, 我们就说 <span class="math inline">\(x\)</span> 为 <span class="math inline">\(N F A(M)\)</span> 所接受 (识别)</p><p><strong>NFA 接受集</strong>:一个 <span class="math inline">\({NFA}(M)\)</span> 所接受的 <span class="math inline">\({V}_{ }^*\)</span> 中的全体字符串称为 <span class="math inline">\(M\)</span> 的接受集或 <span class="math inline">\(M\)</span> 所接受的语言, 记为 <span class="math inline">\({L}(M)\)</span></p><blockquote><p>栗子:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221107161833.png" alt="image-20221107161833337" style="zoom:67%;" / loading="lazy"></p><p>状态转移图:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221010210527.png" alt="image-20221010210527776" style="zoom:80%;" / loading="lazy"></p><p>状态转移矩阵:</p><table><thead><tr class="header"><th>状态</th><th>0</th><th>1</th></tr></thead><tbody><tr class="odd"><td>S0</td><td>{S0, S3}</td><td>{S0, S1}</td></tr><tr class="even"><td>S1</td><td><span class="math inline">\(\varnothing\)</span></td><td>{S2}</td></tr><tr class="odd"><td>S2</td><td>{S2}</td><td>{S2}</td></tr><tr class="even"><td>S3</td><td>{S4}</td><td><span class="math inline">\(\varnothing\)</span></td></tr><tr class="odd"><td>S4</td><td>{S4}</td><td>{S4}</td></tr></tbody></table><p>对于此 NFA,若输入字符串 <code>10010</code>,判断其是否为可被识别的符号串: <span class="math display">\[\begin{aligned}M(S_0, 10010) &=M(S_0, 0010) \cup M(S_1, 0010) \\&=M(S_0, 010) \cup M(S_3, 010) \\&=M(S_0, 10) \cup M(S_3, 10) \cup M(S_4, 10) \\&=M(S_0, 0) \cup M(S_1, 0) \cup M(S_4, 0) \\&=\{S_0, {~S}_3, {~S}_4\}\end{aligned}\]</span> 因为 <span class="math inline">\(M(S_0, 10010)=\{S_0, {~S}_3, {~S}_4\} \cap {Z} \neq \varnothing\)</span>, 所以字符串 10010 为此 NFA 所接受。显然,从状态转换图的初始状态 <span class="math inline">\(S_0\)</span> 出发,有路径至终止状态 <span class="math inline">\(S_4\)</span></p></blockquote><ul><li>显然的,如果一个语言能被 NFA 接受,则一定能被 DFA 接受</li></ul><h4 id="将非确定的有穷自动机确定化的方法">将非确定的有穷自动机确定化的方法</h4><p>上文说到 NFA 之所以不确定,是因为其存在到集合的映射。而将 NFA 转化为 DFA 的关键就在于,将这些 <strong>集合视作新的状态</strong>(其实就是穷举法),下面给出定义:</p><p>设( <span class="math inline">\({NFA}) M=({K}, {V}_, M, S, {Z})\)</span> 是 <span class="math inline">\({V}_\)</span> 上一个 <span class="math inline">\({NFA}\)</span>, 构造一个等价的 <span class="math inline">\((DFA)M^{\prime}=({K}^{\prime}, {V}_, M^{\prime}, S^{\prime}, {Z}^{\prime})\)</span></p><ol type="1"><li><p><span class="math inline">\(K^{\prime}\)</span> 由 <span class="math inline">\(K\)</span> 的全部子集组成, 即 <span class="math inline">\(K^{\prime}=2^K\)</span> (一般除去空集 <span class="math inline">\(\{\varepsilon\}\)</span>,所以 <span class="math inline">\(size(K^\prime) = 2^{size(K)}-1\)</span>)</p><p>例如,若 <span class="math inline">\({K}=\{S_1, {~S}_2, {~S}_3\}\)</span>,则 <span class="math inline">\({K}\)</span> 的一个子集 <span class="math inline">\(\{S_1, {~S}_2\}\)</span> 表示 <span class="math inline">\({K}^{\prime}\)</span> 的一个状态,用记号 <span class="math inline">\([S_1, {~S}_2]\)</span> 表示,也可 <strong>重新命名</strong></p></li><li><p><span class="math inline">\({V}_={V}_\)</span></p></li><li><p><span class="math inline">\(S^{\prime}=[S]\)</span> (例如, <span class="math inline">\(S=\{S_1, {~S}_2\}\)</span>, 则 <span class="math inline">\(S^{\prime}=[S_1, {~S}_2]\)</span> )</p></li><li><p><span class="math inline">\({Z}^{\prime}=\{[S_1, {~S}_2, \cdots, S_][[S_1, {~S}_2, \cdots, S_] \in {K}^{\prime}..\)</span> 且 <span class="math inline">\(.\{S_1, {~S}_2, \cdots, S_\} \cap {Z} \neq \varnothing\}\)</span></p></li><li><p><span class="math inline">\(M^{\prime}([S_1, {~S}_2, \cdots, S_], {a})=[{R}_1, {R}_2, \cdots, {R}_], \quad {a} \in {V}_\)</span></p></li></ol><p>简而言之,状态集合变成原本的 <strong>幂集</strong>,输入集合不变,起始状态不变(换了个符号),终止状态变为只要新的状态集合中含有原本的终止状态即是新的终止状态,映射的改变从 NFA 的 <strong>状态到集合</strong> 的映射变为 <strong>集合到集合</strong> 的映射,这一部分可以看看下面栗子辅助理解</p><blockquote><p><span class="math inline">\((NFA)M=(\{S_0, {~S}_1\},\{a, b\}, M,\{S_0\},\{S_1\})\)</span>,其中 ${K}={S_0, {~S}_1}, V_T=<br /></p></blockquote>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>嘿嘿嘿,boki酱可爱捏</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221107155740.jpg" alt="102545983_p0_master1200" style="zoom: 50%;" /></p></summary>
<category term="编译原理" scheme="http://lapras.xyz/categories/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="形式语言" scheme="http://lapras.xyz/tags/%E5%BD%A2%E5%BC%8F%E8%AF%AD%E8%A8%80/"/>
</entry>
<entry>
<title>汇编语言程序设计(三)</title>
<link href="http://lapras.xyz/2022/10/23/80b4c1fd.html"/>
<id>http://lapras.xyz/2022/10/23/80b4c1fd.html</id>
<published>2022-10-23T12:08:32.390Z</published>
<updated>2022-10-23T12:11:02.205Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220927102559.png" alt="image-20220927102552370" style="zoom:67%;" / loading="lazy"></p><p>完结,准备爽放两个实践周</p><span id="more"></span><h2 id="宏汇编语言基本语句">宏汇编语言基本语句</h2><h3 id="源程序的结构">源程序的结构</h3><p>一个完整的源程序在结构上必须做到:</p><ol type="1"><li>用 <strong>方式选择伪指令</strong> 说明执行该程序的 <strong>微处理器类型</strong></li><li>用 <strong>段定义语句</strong> 定义每一个 <strong>逻辑段</strong></li><li>用 <strong>ASSUME 语句</strong> 说明 <strong>段约定</strong></li><li>用 <strong>过程定义语句</strong> 定义每一个 <strong>子程序</strong></li><li>程序在完成预定功能之后,应能安全 <strong>返回 DOS</strong></li><li>用 <strong>汇编结束语句</strong> 说明源程序结束</li></ol><h3 id="伪指令">伪指令</h3><ol type="1"><li><p>处理器选择伪指令</p><p>格式:<code>.586</code></p><p>功能:通知汇编程序,汇编源程序汇编链接后生成对应哪一种 CPU 类型的机器指令</p><p>注意:方式选择伪指令放在程序的头部,做为源程序的第一条语句。不设置方式选择伪指令与设置 <code>.8086</code> 是等价的</p></li><li><p>段定义伪指令</p><p>格式:</p><pre class="language-asm" data-language="asm"><code class="language-asm">段名 SEGMENT 定位参数 链接参数 '分类名' 段长度...段体...段名 ENDS</code></pre><p>功能:是 <strong>逻辑段</strong> 的定界语句,源程序中每一个逻辑段都必须用段定义语句定界</p><ol type="1"><li><strong>定位参数</strong>:通知链接程序,告诉它逻辑段的目标代码在存储器中如何存储<ol type="1"><li><code>BYTE</code> 字节地址:表明该逻辑段的目标代码可以从任意地址开始依次存放</li><li><code>WORD</code> 字地址:表示该逻辑段的目标代码,从偶地址开始依次存放</li><li><code>PARA</code>(缺省值)节地址:表示该逻辑段的目标代码,从能被 16 整除(末位为 0)的地址开始存放</li><li><code>PAGE</code> 页地址:表示该逻辑段的目标代码,从一个能被 256 整除的地址开始依次存放</li></ol></li><li><strong>链接参数</strong>:通知链接程序对逻辑段的处理方法</li><li><strong>'分类名'</strong>:通知链接程序,把 <strong>分类名</strong> 相同的同名逻辑段组合起来,放在邻近的内存区</li><li><strong>段长度</strong>:说明逻辑段的 <strong>寻址方式</strong> 的位数。若是 <code>USE16</code> ,则表示该段体积最大 64K ,单元偏移地址为 16 位,采用 16 位寻址。若为 <code>USE32</code>,则表示该段体积最大 4G ,单元偏移地址为 32 位,采用 32 位寻址</li></ol><p>注意:</p><ol type="1"><li>一个逻辑段从 <code>SEGMENT</code> 语句开始,到 <code>ENDS</code> 语句结束</li><li>通常用 <code>DATA</code> 做为数据段的段名,用 <code>STACK</code> 做为堆栈段的段名,<code>CODE</code> 为代码段的段名</li><li>在 <strong>实模式环境</strong> 下,各逻辑段应采用 16 位寻址,所以段长度选用 <code>USE16</code></li><li>只有在模块化程序中,才有必要考虑链接参数的选择</li><li>注意分类名记得加 <strong>单引号</strong></li><li>在单一模块程序中,<strong>定位参数</strong>,<strong>链接参数</strong>,<strong>分类名</strong> 均选用 “缺省” 表示即可</li><li>在单一模块程序中,如果有 <strong>堆栈段</strong>,则堆栈段选用 <strong>STACK</strong> 为链接参数(因为只有 STACK 属性才表示该段是堆栈段),<strong>'STACK'</strong> 为分类名,由于选用 STACK 为链接参数,汇编后 DOS 将自动给 <code>SS:SP</code> 赋值</li></ol></li><li><p>段约定伪指令 <code>ASSUME</code></p><p>格式:<code>ASSUME 段寄存器:段名,...,段寄存器:段名</code></p><p>功能:<code>ASSUME</code> 语句通知汇编程序,寻址逻辑段使用哪一个段寄存器</p><p>注意:</p><ol type="1"><li><code>ASSUME</code> 语句通常是放在 <strong>代码段</strong> 的第一条语句</li><li><code>ASSUME</code> 语句,仅仅是约定了对某个逻辑段进行寻址操作时使用哪一个段寄存器,而段寄存器的初值还必须在程序中用 <code>MOV</code> 指令设置</li><li><code>CS:IP</code> 由 DOS 自动赋初值</li><li><code>SS:SP</code> 初值由 DOS 自动赋给,或由程序员赋给</li></ol></li><li><p>过程(子程序)定义伪指令 <code>PROC</code></p><p>格式:</p><pre class="language-asm" data-language="asm"><code class="language-asm">过程名PROC属性...过程实体...RET过程名 ENDP</code></pre><p>功能:定义过程(子程序)</p><ol type="1"><li>属性:两种描述:<code>NEAR</code> 代表段内操作,<code>FAR</code> 代表段间操作</li></ol><p>注意:</p><ol type="1"><li>一个子程序从 <code>PROC</code> 语句开始,到 <code>ENDP</code> 语句结束</li><li>汇编后过程名就是子程序第一条指令的入口地址</li></ol></li><li><p>返回 <code>DOS</code> 指令</p><p>格式:</p><pre class="language-asm" data-language="asm"><code class="language-asm">MOV AH, 4CHINT 21H</code></pre><p>功能:程序在完成预定任务之后,返回 DOS</p><p>注意:事实上是用了下文要介绍的 <strong>系统功能调用</strong></p></li><li><p>汇编结束伪指令 <code>END</code></p><p>格式:<code>END BEGIN</code></p><p>功能:通知汇编程序源程序到此结束</p><p>注意:DOS 装载程序的可执行文件时,自动把标号 <code>BEGIN</code> 所在段的段基址赋给 <code>CS</code>,把 <code>BEGIN</code> 所在单元的偏移量赋给 <code>IP</code>。从而 CPU 自动从 <code>BEGIN</code> 开始的那条指令依次执行程序</p></li></ol><h3 id="汇编源程序的编程格式">汇编源程序的编程格式</h3><p>在汇编语言中,针对于 Microsoft DOS / Windows 操作系统,为了生成 EXE 和 COM 两种不同的文件,在编写源程序时必须依据规定的格式进行,也称为编程格式。分别对应 EXE 文件的编程格式和 COM 文件的编程格式。</p><p> 1. EXE 文件的编程格式:只能生成扩展为 EXE 的可执行文件。允许源程序使用多个逻辑段 (包括数据段、堆栈段、代码段及其它逻辑段) ; 在实模式下,每个逻辑段的目标块不超过 64K ;适合编写大型程序。最终能生成 .EXE 文件 1. COM 文件的编程格式:可以生成扩展为 COM 的可执行文件。源程序只能有一个逻辑段(即代码段),不允许设置堆栈段。且代码段目标块小于 64K,适合于编写中小型程序</p><h2 id="系统功能调用">系统功能调用</h2><p>DOS 的 4 个组成部分中 <code>IBMBIO.COM</code> 和 <code>IBMDOS.COM</code> 是 DOS 系统的核心模块,前者为基本 I/O 设备处理程序,与 BIOS 一起完成数据输入和数据输出的基本操作;后者是磁盘文件管理程序。这两个模块均有若干子功能可以被用户程序调用,在汇编里面要使用一些已经写好的子程序的话,我们就可以使用功能调用。有两种方式:</p><ol type="1"><li>通过操作系统内核里面提供的一些子程序。(也叫做 DOS 功能调用)</li><li>绕开操作系统,直接调用主板芯片上固化的子程序。(叫做 BIOS 功能调用)</li></ol><h3 id="dos-功能调用">DOS 功能调用</h3><p>要条用系统功能的基本格式</p><pre class="language-asm" data-language="asm"><code class="language-asm">MOV AH,功能号设置入口参数INT 21H分析出口参数</code></pre><ol type="1"><li><p><code>01H</code> 键入字符</p><p>格式:</p><pre class="language-asm" data-language="asm"><code class="language-asm">MOV AH, 1HINT 21H</code></pre><p>功能:等待键入一个字符,有回显,并用 <code>Ctrl + C</code> 结束输入</p><p>注意:</p><ol type="1"><li>入口参数:无</li><li>出口参数:<code>AL = 按键的 ASCII 码</code></li><li>若 AL = 0,表明按键是功能键、光标键,需再次调用此功能,才能返回按键的扩展码</li></ol></li><li><p><code>02H</code> 显示字符</p><p>格式:</p><pre class="language-asm" data-language="asm"><code class="language-asm">;在文本屏幕上显示字符 AMOV DL, 'A'MOV AH, 2HINT 21H</code></pre><p>功能:显示一个字符。本功能在屏幕的当前位置显示一个字符,光标右移一格,如果是在一行末尾显示字符,则光标返回下一行的开始格。如果是在屏幕的右下角显示字符,则光标在返回时屏幕要上滚一行</p><p>注意:</p><ol type="1"><li>入口参数:<code>DL = 待显示字符的ASCII码</code></li><li>出口参数:无</li><li>该功能要破坏 AL 寄存器的内容</li><li>注意:将内存中的四位二进制数打印出来时,要先将其转换为对应的<code>ASCII</code>码<ol type="1"><li>数字:<code>+30H</code></li><li>字母(A~F):<code>+37H</code>,因为A是<code>41H</code></li></ol></li></ol></li><li><p><code>09H</code> 显示字符串</p><p>格式:</p><pre class="language-asm" data-language="asm"><code class="language-asm">;在屏幕上显示‘HELLO WORLD!'.486DATA SEGMENT USE16 ; 定义数据段MESG DB 'HELLO WORLD!', '$'DATA ENDSCODE SEGMENT USE16 ; 定义代码段ASSUME CS:CODE, DS:DATABEG: MOV AX, DATAMOV DS, AXLAST: MOV AH, 9MOV DX, OFFSET MESG; 把 MESG 的首地址给 DX 寄存器INT 21HMOV AH, 4CHINT 21H ; 返回DOSCODE ENDSEND BEG</code></pre><p>功能:从屏幕当前位置开始,显示字符串,遇到结束标志 <code>$</code> 时停止</p><p>注意:</p><ol type="1"><li>入口参数:<code>DS:DX</code> 指向字符串首地址,字符串必须以 <code>$</code>(即 ASCII 码 24H ) 为结束标志</li><li>会破坏 <code>AL</code> 寄存器的内容</li><li>一般搭配末位为<code>0DH,0AH,'$'</code>的字符串使用,达到回车换行的目的</li></ol></li><li><p><code>0AH</code> 键入字符串</p><p>格式:</p><pre class="language-asm" data-language="asm"><code class="language-asm">.486DATA SEGMENT USE16 ; 定义数据段 BUF DB 100 DB ? DB 10 DUP(?)DATA ENDSCODE SEGMENT USE16 ; 定义代码段ASSUME CS:CODE, DS:DATABEG: MOV AX, DATAMOV DS, AXMOV DX, OFFSET BUFMOV AH, 0AH; 功能号 0AH INT 21HMOV DX, OFFSET BUF+2; 从"BUF+2"的位置开始键入MOV BX, DX; 键入字符的个数ADD BL, BUF+1MOV BYTE PTR [BX], '$'; 间接寻址MOV AH, 09H; 功能号09H[显示字符串]INT 21HMOV AH, 4CHINT 21H ; 返回DOSCODE ENDSEND BEG</code></pre><p>功能:等待键入一串字符,送用户程序数据缓冲区</p><p>注意:</p><ol type="1"><li>入口参数:<code>DS:DX</code> 指向放键入字符的缓冲区</li><li>出口参数:存放于缓冲区的字符串,以回车键结尾</li><li>缓冲区定义的第二个字节(从1开始数)由系统设置为输入的字符串的长度</li><li>如果输入的字节数少于定义的字节数,缓冲区其余字节将自动补零</li><li>输入的字节数大于定义的字节数,后来输入的字符被自动被丢弃且响铃警告</li></ol></li></ol><h3 id="bios-功能调用">BIOS 功能调用</h3><pre class="language-asm" data-language="asm"><code class="language-asm">MOV AH, 功能号设置入口参数INT XXH (XXH为 BIOS 功能调用类型号,本课只涉及键盘输入功能调用,所以间断号 = 16H,屏幕显示间断号为10H) 分析出口参数</code></pre><ol type="1"><li><p><code>00H</code> 读取键入字符</p><p>格式:</p><pre class="language-asm" data-language="asm"><code class="language-asm">MOV AH, 00HINT 16HMOV CL, AL ; 获得键盘输入字符的 ASCII 码, 并放入 CL 寄存器</code></pre><p>功能:读取键入的一个字符,无回显,响应 <code>Ctrl+C</code>,无键入则等待</p><p>注意:</p><ol type="1"><li>入口参数:无</li><li>出口参数:<code>AL</code> = 键入字符的 ASCII 码。若 <code>AL</code> = 0,则 <code>AH</code> = 输入键的扩展码</li></ol></li><li><p><code>01H</code> 查询键盘缓冲区</p><p>格式:</p><pre class="language-none"><code class="language-none">MOV AH, 01HINT 16HMOV CL, AL ; 获得键盘输入字符的 ASCII 码, 并放入 CL 寄存器</code></pre><p>注意:</p><ol type="1"><li>入口参数:无</li><li>出口参数:<ul><li><code>Z=0</code>,表示有输入,键代码仍留在键盘缓冲区中,此时 <code>AL</code> = 输入字符的 ASCII 码,<code>AH</code> = 输入字符的扩展码。</li><li><code>Z=1</code>,表示无输入</li></ul></li></ol></li></ol><h2 id="子程序与宏程序">子程序与宏程序</h2><h3 id="子程序及其调用">子程序及其调用</h3><p>子程序是相对独立的程序,当程序中要多次完成某一操作时,为了简化整体程序,增强程序可读性,常常把“完成某一操作”设计成一个子程序以供调用</p><ol type="1"><li>子程序用<code>PROC/ENDP</code>分界</li><li>子程序分为:段内子程序、段间子程序、无参数子程序、有参数子程序</li><li>要明确地定义出这个子程序的入口参数和出口参数,使调用者能方便地使用子程序</li><li>在子程序中合理地保存主程序和子程序都用到的寄存器和存储单元,以使主程序能正确地运行</li><li>对子程序传参使用<code>M/R/Stack</code></li></ol><h3 id="宏指令及其调用">宏指令及其调用</h3><p>与C语言的宏命令差不多,都是在编译的时候对代码进行替换。格式如下</p><pre class="language-asm" data-language="asm"><code class="language-asm">MacroNameMACRO哑元表LOCAL Grade1,Grade2MacroBodyENDM</code></pre><ol type="1"><li>含有哑元表表示该宏指令带参,用一串逗号间隔形式的参数表(无值符号,调用时用<code>R/M/N</code>替换,M无需PTR运算符)</li><li><code>MACRO/ENDM</code>是宏体的定界语句</li><li>在代码段中放置一条宏指令就是宏调用</li><li>使用<code>LOCAL</code>伪指令解决宏指令中标号重复定义的错误,<code>LOCAL 标号名表</code></li></ol><p>以下是一个输入输出字符加了换行宏指令的小程序</p><pre class="language-asm" data-language="asm"><code class="language-asm">.486DATA SEGMENT USE16 BUF DB ?DATA ENDSCRLF MACRO MOV AH,2 MOV DL,0DH INT 21H MOV DL,0AH INT 21HENDMCODE SEGMENT USE16 ASSUME CS:CODE, DS:DATA BEG: MOV AX,DATA MOV DS,AX ;赋予段基址 MOV AH,1 INT 21H ;调用DOS键入功能 MOV BUF,AL ;传递给BUF MOV DL,BUF ;传递给DL MOV AH,2 INT 21H ;调用DOS输出功能 CRLF MOV AH,4CH INT 21H ;结束CODE ENDSEND BEG</code></pre><h3 id="小结">小结</h3><p>共同点:</p><ol type="1"><li>宏指令与子程序都可以简化程序设计,增强程序的可读性</li></ol><p>不同点:</p><ol type="1"><li>子程序调用是由CPU完成的,宏指令调用是在汇编过程中由汇编程序完成的</li><li>子程序调用可以减小目标程序的体积,宏指令则不能</li></ol><h2 id="程序设计举例">程序设计举例</h2><h3 id="顺序程序设计">顺序程序设计</h3><h4 id="读取字符">读取字符</h4><blockquote><p>从键盘输入一个字符并存储到字节变量BUF单元中并输出</p></blockquote><p>分析:用DOS功能调用完成键入,输入的字符保存在<code>AL</code>寄存器中;将<code>AL</code>中的内容存到定义在数据段的变量<code>BUF</code>所指单元中,然后再用DOS 02H功能,(注意其入口参数为DL)输出到屏幕上</p><pre class="language-asm" data-language="asm"><code class="language-asm">.486DATA SEGMENT USE16 BUF DB ?DATA ENDSCODE SEGMENT USE16 ASSUME CS:CODE, DS:DATA BEG: MOV AX,DATA MOV DS,AX ;赋予段基址 MOV AH,1 INT 21H ;调用DOS键入功能 MOV BUF,AL ;传递给BUF MOV DL,BUF ;传递给DL MOV AH,2 INT 21H ;调用DOS输出功能 MOV AH,4CH INT 21H ;结束CODE ENDSEND BEG</code></pre><h3 id="分支程序设计">分支程序设计</h3><h4 id="分段函数">分段函数</h4><blockquote><p>根据给定分段函数实现输入输出: <span class="math display">\[y=f(x)=\left\{\begin{array}{cc}1 & x>0 \\0 & x=0 \\-1 & x<0\end{array}\right.\]</span></p></blockquote><p>分析:判定符号位<span class="math inline">\(S\)</span>和全零位<span class="math inline">\(Z\)</span>即可,可以用<span class="math inline">\(ADD\ 0\)</span>或者<span class="math inline">\(OR\)</span>自己刷新符号位</p><pre class="language-asm" data-language="asm"><code class="language-asm">.486DATA SEGMENT USE16 X DW ? MSG_0 DB '0$' MSG_1 DB '1$' MSG__1 DB '-1$'DATA ENDSCODE SEGMENT USE16 ASSUME CS:CODE,DS:DATA BEG: MOV AX,DATA MOV DS,AX ;段基址 MOV X,12 ;设定X的初始值 MOV AX,X OR AX,AX ;利用位运算进行符号位判定 也可使用 ADD AX,0 JZ ZERO ;全0进入 JNS PLUS ;正数进入 ;负数情况 MOV BL,0FFH ;补码全F为-1 MOV DX, OFFSET MSG__1 JMP EXIT ;结束 ZERO: MOV BL,0 MOV DX, OFFSET MSG_0 JMP EXIT PLUS: MOV BL,1 MOV DX, OFFSET MSG_1 EXIT: MOV AH,09H INT 21H ;字符串输出 MOV AH,4CH INT 21HCODE ENDSEND BEG</code></pre><h4 id="打印二进制数字">打印二进制数字</h4><blockquote><p>将BX寄存器的内容以二进制数格式显在屏幕上</p></blockquote><p>思路:通过逻辑左移,每次取最高位,用标识符判定最高位,然后打印。循环数字二进制长度次即可</p><pre class="language-asm" data-language="asm"><code class="language-asm">.486 CODE SEGMENT USE16 ASSUME CS:CODE BEG: MOV BX,5678H ;初始化要打印的数字 MOV CX,16 ;16位 LAST: MOV DL,'0' ;默认此位为0 ROL BX,1 ;逻辑左移一位 JNC NEXT ;若C标(移出的位)为0进入 MOV DL,'1' ;否则声明此位为1 NEXT: MOV AH,2H ;打印 INT 21H LOOP LAST MOV AH,4CH INT 21HCODE ENDSEND BEG</code></pre><h3 id="循环程序设计">循环程序设计</h3><h4 id="求累加和">求累加和</h4><blockquote><p>求1-10的累加和</p></blockquote><p>思路:利用<code>LOOP</code>和<code>CX</code>这个天然的整数列,变为从10加到1</p><pre class="language-asm" data-language="asm"><code class="language-asm">.486DATA SEGMENT USE16 SUM DW ?DATA ENDSCODE SEGMENT USE16 ASSUME CS:CODE,DS:DATA BEG: ;初始化 MOV AX,DATA MOV DS,AX MOV CX,10 MOV AX,0 AGA: ADD AX,CX LOOP AGA MOV SUM,AX MOV AH,4CH INT 21HCODE ENDS</code></pre><h4 id="禁忌的双重循环">禁忌的双重循环</h4><blockquote><p>双重循环:外层循环次数为<span class="math inline">\(N\)</span>,内层循环次数为<span class="math inline">\(M\)</span></p><p>要求:将<code>DATA</code>段中的每个单词改为大写字母</p><pre class="language-asm" data-language="asm"><code class="language-asm">>data segmentdb 'ibm 'db 'dec 'db 'dos 'db 'vax '>data ends</code></pre></blockquote><p>思路:</p><ol type="1"><li><strong>小写字母改大写字母</strong>可以用<code>AND 11011111B</code>即将<span class="math inline">\(D_5\)</span>置0;反之,<strong>大写字母变小写字母</strong>只用<code>OR 00100000B</code>将<span class="math inline">\(D_5\)</span>置1即可</li><li>使用4*3的二重循环,先定位行,再定位列。例如,先定位第一行,再循环修改前三列,再定位下一行,循环修改前三列…所以这里应该要使用<code>[BX+SI]</code>这种基址变址寻址的方式,用<code>BX</code>定位字符串,用<code>SI</code>定位字符</li><li>:star:值得注意的是,每次开始内层循环时,必须将外层循环的<code>CX</code>保存在栈中。在执行外层<code>LOOP</code>指令前恢复<code>CX</code></li></ol><pre class="language-asm" data-language="asm"><code class="language-asm">DATA SEGMENT DB 'IBM ' ;长度为3+13 DB 'DEC ' DB 'DOS ' DB 'VAX 'DATA ENDSSTACK SEGMENT ;定义堆栈段,容量为16BYTE DB 16 DUP(?)STACK ENDSCODE SEGMENT ASSUME CS:CODE,DS:DATA,SS:STACK START: MOV AX,STACK MOV SS,AX MOV SP,16 ;初始化堆栈 MOV AX,DATA MOV DS,AX MOV BX,0 ;BX指向字符串首地址 MOV CX,4 ;外层循环次数为4 S0: PUSH CX ;外层循环的CX值压栈 MOV SI,0 ;变址寄存器SI指向第一个字符 MOV CX,3 ;内层循环次数为3 S: MOV AL,[BX+SI] ;变址寻址 AND AL,11011111B ;将小写字母转换为大写字母 MOV [BX+SI],AL INC SI ;变址寄存器SI指向下一个字符 LOOP S ;内层继续循环 ADD BX,16 ;BX指向下一个字符串 POP CX ;从栈顶弹出原CX的值,恢复CX LOOP S0 ;外层循环的LOOP指令将CX中的计数值减1 MOV AX,4C00H INT 21HCODE ENDSEND START</code></pre><h4 id="寻找最大字符">寻找最大字符</h4><blockquote><p>假设从BUF单元开始为一个字符串ASCII码,找出其中的最大数送屏幕显示</p></blockquote><p>思路:</p><ol type="1"><li>由于只有一个字符串,所以寻址直接使用基址寻址<code>[BX]</code>即可</li><li>统计字符串长度以确定循环次数<code>CX</code></li></ol><pre class="language-asm" data-language="asm"><code class="language-asm">.486DATA SEGMENT USE16 BUF DB 'QWERTYUOIOPASDFGHJKLZXCVBNM1234567890' BUF_LEN = $-BUF ;字符串长度 MSG DB 'The max number is : $'DATA ENDSCODE SEGMENT USE16 ASSUME CS:CODE,DS:DATA BEG: MOV AX,DATA MOV DS,AX MOV AL,0 ; AL存放最大字符,初始化为最小值0 MOV CX,BUF_LEN ; CX存放字符串长度 LEA BX,BUF ; BX指向字符串首地址 LAST: CMP AL,[BX] ; 比较AL和字符串中的字符 JAE NEXT ; 如果AL大于等于字符串中的字符,跳转到NEXT MOV AL,[BX] ; 否则将字符串中的字符赋值给AL NEXT: INC BX ; BX指向下一个字符 LOOP LAST ; 循环 MOV DX,OFFSET MSG ; 设定出口参数DX MOV MSG+19,AL ; 将最大字符存放到MSG中 MOV AH,09H ;显示结果 INT 21H MOV AH,4CH INT 21HCODE ENDSEND BEG</code></pre><h3 id="数制转换">数制转换</h3><h4 id="二进制数显示">二进制数显示</h4><p>要求键盘输入一位数(0~9),转换为等值的二进制数显示</p><pre class="language-asm" data-language="asm"><code class="language-asm">.486DATA SEGMENT USE16 MESG1 DB 'Please Enter ! ',0DH,0AH,'$' MESG2 DB 'Error ! $'DATA ENDSCODE SEGMENT USFE16 ASSUME CS:CODE,DS:DATA BEG: MOV AX,DATA MOV DS,AX ;段寄存器 MOV AH,9 MOV DX,OFFSET MESG1 INT 21H ;显示操作提示 MOV AH,1 INT 21H ;键入一个字符,存入AL CMP AL,3AH JNC ERROR ;>'9'转 CMP AL,30H JC ERROR ;<'0'转 SUB AL,30H MOV BL,AL ;BL=0~9 的二进制数 MOV AH,2 MOV DL,'=' INT 21H CALL DISP MOV AH,2 MOV DL,'B' INT 21H JMP EXIT ERROR:MOV AH,9 LEA DX, MESG2 INT 21H ;显示错误信息 EXIT: MOV AH,4CH INT 21HDISP PROC ;显示BL中的二进制数 MOV CX,8 LAST: MOV DL,'0' RCL BL,1 JNC NEXT MOV DL,'1' NEXT: MOV AH,2 INT 21H LOOP LAST RETDISP ENDPCODE ENDSEND BEG </code></pre><h4 id="二进制数转十六进制数显示">二进制数转十六进制数显示</h4><p>从内存<code>BNUM</code>单元开始,有4个16位的二进制数,要求把它们转换成16进制数,并送屏幕显示。</p><p>思路:因为每组16进制数的长度是4*4 = 16位,所以这里在大循环的时候,使用<code>EDX</code>存放每一组数字,并先令其左移16位方便后续循环操作。后续的小循环操作简而言之就是:</p><ol type="1"><li>每次循环左移4位,这样要解析的字符就到DL的低四位中,并通过一个<code>AND</code>取数值</li><li>取出数制后,根据其大小进行<code>+30H</code>和<code>+37H</code></li><li>保存其值到输出缓冲区<code>[SI]</code>,如此往复,直到计数器为0</li></ol><pre class="language-asm" data-language="asm"><code class="language-asm">;对内存中的二进制数转换为16进制数.486DATA SEGMENT USE16 BNUM DW 0001001000110100B ;1234H DW 0101011001111000B ;5678H DW 0001101000101011B ;1A2BH DW 0011110001001101B ;3C4DH BUF DB 4 DUP(?), 'H$' ;输出缓冲区 COUNT DB 4DATA ENDSCODE SEGMENT USE16 ASSUME CS:CODE, DS:DATA BEG: MOV AX,DATA MOV DS,AX ;初始化 MOV CX,4 ;外层大循环为4个数 MOV BX,OFFSET BNUM ;BX作为基址寄存器,指向各个二进制数的首地址 AGA: MOV DX,[BX] SAL EDX,16 ;算术左移(事实上随意左移即可,因为目的是将其数字位于最高位)16位,将低16位移到高16位 CALL N2_16ASC ;调用函数后,缓冲区中有一组十六进制数 MOV AH,9 MOV DX,OFFSET BUF INT 21H ;输出一组十六进制数 ADD BX,2 ;地址加 2,指向下一个二进制数 LOOP AGA ;循环4(CX)次 MOV AH,4CH INT 21H ;二进数→十六进数ACSII码N2_16ASC PROC MOV SI,OFFSET BUF ;输出缓冲区地址→SI MOV COUNT,4 ;重置COUNT为4,因为每组16进制数有4个字符 LAST: ROL EDX,4 ;循环左移4位 AND DL,0FH ;00001111B ;取低4位置入DL CMP DL,10 ;令DL与10比较 JC NEXT ;如果DL<10,则跳转到NEXT ADD DL,7 ;如果DL>=10,则准备+37H变为字母的ASCII NEXT: ADD DL,30H ;DL+30H MOV [SI],DL ;将数字保存到输出缓冲区 INC SI ;输出缓冲区地址加1 DEC COUNT ;计数-1 JNZ LAST ;如果计数不为0,则跳转到LAST RETN2_16ASC ENDPCODE ENDSEND BEG</code></pre><h4 id="二进制数转十进制数显示">二进制数转十进制数显示</h4><p>思路:<strong>比较法!</strong>比如8位二进制数最大为<span class="math inline">\(11111111B\)</span>即<span class="math inline">\(255\)</span>,那么可以通过判断二进制数含有几个100,几个10,几个1来对其进行输出</p><pre class="language-asm" data-language="asm"><code class="language-asm">.486CMPDISP MACRO NN ;宏定义 LOCAL LAST, NEXT ;定义局部标号 MOV DL,0 ;DL清0 LAST: CMP BEN, NN ;比较 JC NEXT ;BEN<NN,跳转到NEXT INC DL ;DL加1 SUB BEN, NN ;BEN减去NN JMP LAST ;循环 NEXT: ADD DL, 30H MOV AH,2 INT 21H ;显示ENDMCODE SEGMENT USE16 ASSUME CS:CODE BEN DB 10101110B ;=174 BEG: CMPDISP 100 ;调用宏判断并输出100的个数 CMPDISP 10 ;调用宏判断并输出10的个数 CMPDISP 1 ;调用宏判断并输出1的个数 MOV AH,4CH INT 21HCODE ENDSEND BEG</code></pre><h3 id="实验内容">实验内容</h3><h4 id="统计字符串">统计字符串</h4><p>从<code>BUF</code>单元开始存有一字符串(长度<255),编程实现统计该字符串中的<code>ASCII</code>在<span class="math inline">\(42H~45H\)</span>之间的字符个数,并将统计结果以二进制形式显示在屏幕</p><pre class="language-asm" data-language="asm"><code class="language-asm">.486 DATA SEGMENT USE16 HINT DB 'the BUF is QWERTYUOIOPASDFGHJKLZXCVBNM1234567890',0DH,0AH,'$' BUF DB 'QWERTYUOIOPASDFGHJKLZXCVBNM1234567890' BUF_LEN = $-BUF ;字符串长度 MSG DB 'The number of char that between 42H(B) to 45H(E) = ',0DH,0AH,'$'DATA ENDSCODE SEGMENT USE16 ASSUME CS:CODE,DS:DATA BEG: MOV AX,DATA MOV DS,AX MOV DX, OFFSET HINT MOV AH,09H INT 21H MOV CX,BUF_LEN ; CX存放字符串长度 LEA BX,BUF ; BX指向字符串首地址 MOV AL,0 ; ans LAST: CMP BYTE PTR [BX],42H ;注意指定大小,否则BX默认为WORD JC NEXT ;低于42H则转移 CMP BYTE PTR [BX],45H ;注意指定大小,否则BX默认为WORD JA NEXT ;高于45H则转移 INC AL ;答案+1 NEXT: INC BX ; BX指向下一个字符 LOOP LAST ; 循环 ADD AL,30H ;将数字转换为字符 MOV DX,OFFSET MSG ; 设定出口参数DX MOV MSG+51,AL ; 将最大字符存放到MSG中 MOV AH,09H ;显示结果 INT 21H MOV AH,4CH INT 21HCODE ENDSEND BEG</code></pre><h4 id="编写登录验证程序">编写登录验证程序</h4><p>程序执行后,给出提示操作,请用户键入用户名和密码;用户在键入密码时,程序不回显键入字符而是显示<code>*</code>;只有当用户键入的用户名,密码字符串和程序内定的字符串相同时,显示欢迎界面并返回DOS;否则给出提示信息,用户名或密码错误,再次输入</p><pre class="language-asm" data-language="asm"><code class="language-asm">;编写登录验证程序.486DATA SEGMENT USE16 USERNAME1 DB 30,?,30 DUP(?) ;定义字符串,规定第二个字节为字符串长度 USERNAME2 DB 'B20030620' LEN_USERNAME EQU $-USERNAME2 ;计算字符串长度 PASSWORD1 DB 30 DUP(?) ;定义字符串 PASSWORD2 DB 'PASSWORD' LEN_PASSWORD EQU $-PASSWORD2 ;计算字符串长度 PROMOTE1 DB 'Please input username:$' PROMOTE2 DB 'Please input password:$' PROMOTE3 DB 'Login success!$' PROMOTE4 DB 'Login failed!please try again!$' FLAG DB 'Y'DATA ENDS;宏指令显示回车CRLF MACRO MOV AH,2 MOV DL,0DH INT 21H MOV DL,0AH INT 21HENDM;宏指令显示字符串PRINTSTR MACRO STR MOV AH,9 LEA DX,STR INT 21HENDM;宏指令显示字符PRINTCHAR MACRO CHAR MOV AH,2 MOV DL,CHAR INT 21HENDMCODE SEGMENT USE16 ASSUME CS:CODE,DS:DATA,ES:DATA BEG: MOV AX,DATA MOV DS,AX MOV ES,AX ;INIT MOV FLAG,'Y' ;初始化标志位 MOV CX,LEN_USERNAME PRINTSTR PROMOTE1 ;提示输入用户名 MOV AH,0AH MOV DX,OFFSET USERNAME1 ;入口函数DX将字符串存入USERNAME1 INT 21H ;键入字符串 CRLF ;显示回车 MOV CL,USERNAME1+1 ;CL=字符串长度,第2个字节为输入的字符串的长度 MOV CH,0 ;CH=0 CMP CX,LEN_USERNAME ;比较输入的字符串长度与LEN_USERNAME JE NEXT1 ;如果相等,就不设置 MOV FLAG,'1' ;如果不相等,就设置FLAG为N ; JMP FAIL ;DEBUG NEXT1: MOV DI,OFFSET USERNAME2 ;DI指向USERNAME2 MOV SI,OFFSET USERNAME1+2 ;SI指向USERNAME1+2 REPE CMPSB ; 比较字符串 JE NEXT2 ;如果不相等,设置FLAG为N MOV FLAG,'2' ;如果相等,继续 ; JMP FAIL ;DEBUG NEXT2: PRINTSTR PROMOTE2 ;提示输入密码 MOV BX,0 ;使用BX统计密码长度 MOV SI,OFFSET PASSWORD1 ;SI指向PASSWORD1 ;使用07H功能键入密码 GETPW: MOV AH,00H ; 逐个字符输入密码,遇到回车结束输入 INT 16H CMP AL,0DH ;回车输入完毕 JE NEXT CMP AL,08H ;退格键 JE BACK MOV [SI],AL ;将输入的字符存入PASSWORD1 PRINTCHAR '*' ;显示* INC SI INC BX ; 统计输入密码的长度 JMP GETPW BACK: CMP BX,0 ;如果密码长度为0,就不退格 JE GETPW MOV AH,2 MOV DL,08H INT 21H ;显示退格 MOV AH,2 MOV DL,20H INT 21H ;显示空格,覆盖* MOV AH,2 MOV DL,08H INT 21H ;显示退格,回到*之前的位置 DEC SI DEC BX JMP GETPW NEXT: CRLF MOV SI,OFFSET PASSWORD1 ; ;DEBUG 输出密码 ; MOV CX,BX ; DE: ; PRINTCHAR [SI] ; INC SI ; LOOP DE ; CRLF ; ;输出用户名 ; MOV CL,USERNAME1+1 ; MOV SI,OFFSET USERNAME1+2 ; DE1: ; PRINTCHAR [SI] ; INC SI ; LOOP DE1 ; CRLF ; ;DEBUG CMP BX,LEN_PASSWORD ;比较输入的字符串长度与LEN_PASSWORD JE NEXT3 MOV FLAG,'3' ; JMP FAIL ;DEBUG NEXT3: MOV CX,BX MOV DI,OFFSET PASSWORD2 MOV SI,OFFSET PASSWORD1 CHECK: MOV BL,[SI] CMP BL,[DI] JNE CHECKF INC SI INC DI LOOP CHECK JMP NEXT4 CHECKF:MOV FLAG,'4' ; JMP FAIL ;DEBUG NEXT4: CMP FLAG,'Y' ;如果FLAG为Y,就显示登录成功,否则显示登录失败 JNE FAIL PRINTSTR PROMOTE3 CRLF JMP EXIT FAIL: ;输出flag ; PRINTCHAR FLAG CRLF PRINTSTR PROMOTE4 CRLF JMP BEG EXIT: MOV AH,4CH INT 21HCODE ENDSEND BEG</code></pre>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220927102559.png" alt="image-20220927102552370" style="zoom:67%;" /></p>
<p>完结,准备爽放两个实践周</p></summary>
<category term="汇编语言程序设计" scheme="http://lapras.xyz/categories/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="汇编" scheme="http://lapras.xyz/tags/%E6%B1%87%E7%BC%96/"/>
</entry>
<entry>
<title>Games101-Shading</title>
<link href="http://lapras.xyz/2022/10/02/ecdaa9d1.html"/>
<id>http://lapras.xyz/2022/10/02/ecdaa9d1.html</id>
<published>2022-10-02T05:24:35.563Z</published>
<updated>2022-10-02T11:11:21.542Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>Shading is the process of applying a material to an object</p><span id="more"></span><p>在光栅化之后,我们已经能将物体正确的显示在屏幕上,但其颜色却并不“真实”。因而我们现需要对其进行着色(Shading)。课程里面只介绍了一种着色模型:Blinn-Phong 反射模型</p><h2 id="blinn-phong-光照模型">Blinn-Phong 光照模型</h2><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929220613.png" alt="image-20220929220613081" style="zoom: 67%;" / loading="lazy"></p><p>光照模型,简而言之就是描述光线在物体表面反射效果的模型。比如观察这张图的光影,可以将其分为高光(Specular highlights),漫反射(Diffuse reflection)以及环境光(Ambient lighting)三个部分</p><p>取物体表面的一个点进行分析</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929220944.png" alt="image-20220929220944052" style="zoom:80%;" / loading="lazy"></p><ul><li><span class="math inline">\(\vec v\)</span>表示观测方向,即光传播入观测者的方向</li><li><span class="math inline">\(\vec n\)</span>表示该表面的法线</li><li><span class="math inline">\(\vec l\)</span>表示光的入射方向(这里为了计算表示方便,使其从反射点指向光源)</li><li>显然以上三个向量都是表示方向的方向向量</li></ul><p>除此之外,物体表面的亮度取决于从光源到物体表面发射的能量光的大小,显然同一个点上的能量 <span class="math inline">\(I\)</span> 随着光不断向外传播而逐渐减少, 根据能量守恒定律,每一个球面上的总能量都是相同的,很容易推出 <span class="math display">\[r^24\pi I = r'^2 4\pi I'\]</span> 再假设初始 <span class="math inline">\(r =1\)</span>,那么 <span class="math inline">\(I' = I/r^2\)</span>,这便是光源入射到某单位面积处的总能量</p><p>根据朗伯余弦定律(Lambert's Cosine Law),如果入射光束与接受面存在夹角的情况,那么其接受到的总能量需要打一个折扣,即实际接受光的面积 <span class="math inline">\(A = A'\cos \theta\)</span>。换言之,垂直于物体表面的光束分量被全部吸收,平行于物体表面的光束分量被全部无视,那么假定到达表面的光的能量为 <span class="math inline">\(I'\)</span>,那么被表面吸收的为入射光的垂直分量,即 <span class="math inline">\(I'\lvert \vec l \rvert\ \cos \theta\)</span> ,同时 <span class="math inline">\(\vec n \cdot \vec l = \lvert \vec n \rvert\lvert \vec l \rvert \cos \theta\)</span>,而两者同为单位向量,所以 <span class="math inline">\(I'' = I'\cdot \vec n \cdot \vec l\)</span>,那不妨假设单位面积接受到的能量为 1,最终的公式就是简单的 <span class="math inline">\(\vec n \cdot \vec l\)</span></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929222525.png" alt="image-20220929222525445" style="zoom:80%;" / loading="lazy"></p><p>除了反射到 <strong>点</strong> 的能量,物体表面的材质也会对该点对 <strong>光的吸收率</strong> 产生影响,取一反射系数 <span class="math inline">\(K\)</span>,当 <span class="math inline">\(K = 1\)</span> 时表示完全不吸收能量(光全部反射,最终呈现为白色),反之则全部吸收呈现为黑色。</p><h3 id="漫反射">漫反射</h3><blockquote><p><strong>漫反射</strong>(简称 <strong>漫射</strong>,英语:diffuse reflection )是指当一束平行的入射光线射到粗糙的表面时,粗糙的表面会把光线向著各个方向反射的现象。虽然入射线互相平行,由于粗糙的表面上的各点的 <a href="https://zh.m.wikipedia.org/wiki/法线">法线</a> 方向不一致,造成反射光线向不同的方向无规则地反射。这种反射的光称为漫射光</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929221524.png" alt="image-20220929221524483" style="zoom:80%;" / loading="lazy"></p></blockquote><p>直接给出漫反射的 Blinn-Phong 公式: <span class="math display">\[L_d = k_d (I/r^2) max(0,\vec n \cdot \vec l)\]</span></p><ul><li>漫反射由于是光向四周均匀反射产生的,所以与观察方向无关</li><li><span class="math inline">\(k_d\)</span>即漫反射系数</li><li>这里的 <code>max</code> 函数是为了当向量点乘为负数的时,即光从背面穿过了物体达到了 shading point,显然这是没有物理意义的,应该视为在光源背面的点(为 0)</li></ul><h3 id="高光">高光</h3><p>高光(Specular highlights),我们都知道光线在照射到理想镜面上时,反射光与法线的夹角和入射光与法线的夹角相同,记反射光的方向为 <span class="math inline">\(\vec R\)</span>。但由于现实世界并不存在理想镜面,所以当 <span class="math inline">\(\vec R\)</span> 和 <span class="math inline">\(\vec v\)</span> 足够接近时我们就能看到所谓高光</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929225709.png" alt="image-20220929225709233" style="zoom:80%;" / loading="lazy"></p><p>那么我们就可以用 <span class="math inline">\(\vec R \cdot \vec v\)</span> 的方式来判断它们的接近程度(都是单位向量,点乘越接近 1 为越靠近),但计算 <span class="math inline">\(\vec R\)</span> 的过程相对繁琐,所以我们选择用法线方向 <span class="math inline">\(\vec n\)</span> 和半程向量 <span class="math inline">\(\vec h\)</span> 来刻画。所谓半程向量,也就是两个共起点向量的角平分线所在的方向,这里的 <span class="math inline">\(\vec h\)</span> 就是入射光 <span class="math inline">\(\vec l\)</span> 与观察视角 <span class="math inline">\(\vec v\)</span> 的半程向量。之所以可以这么等价,是因为 <span class="math inline">\(\vec n\)</span> 在理想状态下就是 <span class="math inline">\(\vec l\)</span> 和 <span class="math inline">\(\vec v\)</span> 的半程向量</p><p>给出高光的 Blinn-Phong 模型公式 <span class="math display">\[L_s = k_s(I/r^2)max(0,\vec n\cdot \vec h)^p\]</span></p><ul><li>此公式中没有和漫反射项一样去计算表面接收光能的比例的原因是简化计算(经验性公式)</li><li><span class="math inline">\(k_s\)</span>即为镜面反射系数</li><li><code>max</code> 的理由同漫反射,不过这里注意这里描述的是法线和半程向量方向的接近程度</li><li>指数<span class="math inline">\(p\)</span>的存在是为了加速高光对观测角度变化的敏感程度(在 Blinn-Phong 模型中一般使用 100~200)</li></ul><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929231136.png" alt="image-20220929231136051" style="zoom:80%;" / loading="lazy"></p><ul><li>随着镜面反射系数<span class="math inline">\(k_s\)</span>的增大,物体高光范围越大</li><li>随着指数<span class="math inline">\(p\)</span>的增长,物体高光范围会越来越小</li></ul><h3 id="环境光照">环境光照</h3><p>在 Blinn-Phong 这个经验性的模型中,环境光(Ambient Term)项就比较简单。指的是物体表面一点接收到的来自周围物体反射过来四面八方的反射光,是一种 <strong>间接光照</strong>。<strong>它与光的入射方向和观测方向均无关</strong>,在 Blinn-Phong 模型中是一个 <strong>常数</strong>,起增加整体亮度的作用。假设任何一个点所接收到的来自环境的光永远都是相同的,我们记作 <span class="math inline">\(I_a\)</span>,再给定一个系数就 <span class="math inline">\(k_a\)</span> 可以直接近似地来得到环境光 <span class="math display">\[L_a = k_a I_a\]</span></p><h3 id="叠加效果">叠加效果</h3><p>终于,把这三者叠加在一起就得到了完整的 Blinn-Phong 光照模型,其效果如下</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929231730.png" alt="image-20220929231730319" style="zoom:80%;" / loading="lazy"> <span class="math display">\[\begin{aligned}L &= L_a+L_d+L_s \\&= k_a I_a+k_d\left(I / r^2\right) \max (0, {n} \cdot {l})+k_s\left(I / r^2\right) \max (0, {n} \cdot {h})^p\end{aligned}\]</span></p><h2 id="着色频率">着色频率</h2><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002100503.png" alt="image-20221002100456257" style="zoom:80%;" / loading="lazy"></p><p>观察上面这幅图,发现其拥有相同的几何细节,但是呈现出的观感却不尽相同。这是因为其着色频率(Shading Frequencies)或者说着色的对象并不一致。从左到右,依次是:逐平面(Flat Shading),逐顶点(Gouraud Shading)以及逐像素(Phong Shading)。本质上来说是对着色的最小单位选取的不同导致其差异</p><blockquote><p>这三种着色具体的区别其实也取决于具体的模型,并不是说 Flat Shading 就一定会很差,下图中,每一行的模型都是完全一样的,每一列是不同网格顶点数的区别,也就是说当我们的几何模型相对复杂的话,其实也可以用一些相对简单的着色模型,而且得到的结果还可以。着色频率取决于面、点数量。当然,Phong Shading 的着色效果好,其计算量当然也比 Flat Shading 大很多(但也不绝对,如果面数超过了像素数那么 Phong Shading 可能更小),所以具体用哪种着色方法要取决于具体的物体。当面数不是特别多的情况下,Phong Shading 能得到一个较好的结果</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002103000.png" alt="image-20221002103000530" style="zoom: 67%;" / loading="lazy"></p></blockquote><h3 id="平面着色">平面着色</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002101004.png" alt="image-20221002101004726" style="zoom:80%;" / loading="lazy"></p><p>平面着色/逐平面着色(Flat Shading)是指每一个三角形组成一个平面,求每个三角形的法线只需要将三角形的两个边做 <strong>叉积</strong> 即可,但自然在三角形内部不会有着色的变化,也就是说每个三角形面内各点的颜色是完全一样的。这就造成了观感上会看见许多的三角形</p><h3 id="高洛德着色">高洛德着色</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002101259.png" alt="image-20221002101258949" style="zoom:80%;" / loading="lazy"></p><p>高洛德着色/逐顶点着色(Gouraud Shading)是指对每个顶点求取其法向量,然后通过 <strong>重心坐标插值</strong>(后续介绍)的方法求出三角形内部的颜色以实现点与点之间颜色的平滑过渡</p><h4 id="平均周边面法线">平均周边面法线</h4><p>那么如何求取每个顶点的法向量呢,我们知道模型最基本的单位是三角形,建模其实就是用一个个三角形去“拟合”出我们想要的模型,那么那么如果直到每个三角形拟合的对象,比如</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002104528.png" alt="image-20221002104528472" style="zoom:80%;" / loading="lazy"></p><p>那每个三角形的顶点法向量自然就是与球心的反向延长线,但实际上我们不可能直到每个三角形背后想要表示的具体图形是什么。所以就必须 <strong>从三角形中推断出顶点法线</strong>,所以介绍一个方法:平均周边面法线(average surrounding face normals)</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002110712.png" alt="image-20221002110712818" style="zoom:80%;" / loading="lazy"></p><p>也就是使用顶点周围的三角形的法线,做一个加权平均,权重可以为三角形的面积(也许并不科学,但是效果好。正所谓“如果它看上去是对的,那么它就是对的”) <span class="math display">\[N_v =\frac{\sum_i w_iN_i}{\left\|\sum_i w_iN_i\right\|}\]</span></p><h3 id="冯氏着色">冯氏着色</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002102753.png" alt="image-20221002102753113" style="zoom:80%;" / loading="lazy"></p><p>冯氏着色/逐像素着色(Phong Shading)是指对每个像素计算一次光照,像素的法向量是通过 <strong>顶点法向量插值</strong> 得到的,冯氏着色最接近现实,可以在减少三角面数的情况下达到相同的效果(插值后法向量会光滑变化)</p><ul><li>注意 Blinn-Phong 是一种着色模型,这里的 Phong Shading 指的是着色频率</li></ul><h2 id="图形管线">图形管线</h2><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002113315.png" alt="image-20221002113315867" style="zoom:80%;" / loading="lazy"></p><p>图形管线(Pipeline)的整个过程其实就是从三维场景到最后看到的二维像素的过程。而这个过程是已经在硬件里写好了,显卡所做的整个的操作就是这样的操作。</p><ol type="1"><li>Vertex Processing:对空间中每一个顶点做 MVP 变换</li><li>Triangle Processing:形成三角形</li><li>Rasterization:对每个像素采样判断是否在三角形内,即光栅化</li><li>Fragment Processing:判定像素是否可见(也可以归为光栅化)</li><li>Shading:Shading 可以发生在 <strong>顶点处理</strong>(逐顶点着色)上也可以发生在 Fragment 处理(逐像素着色)上</li></ol><h2 id="纹理">纹理</h2><p>那么现在,模型的光照已经确定下来了,也就是模型拥有了明暗细节的变化。但是更多的信息(属性)该如何体现?比如漫反射系数,颜色等等</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002114725.png" alt="image-20221002114725183" style="zoom:80%;" / loading="lazy"></p><p>可以看到,这个丑不拉几的独眼巨人在套上了纹理(贴图)之后就丑的更有细节了,套用纹理的过程如下:</p><ol type="1"><li>美术大大使用 <span class="math inline">\(uv\)</span> 展开,将绘制好的三维图像映射到二维的 texture space(具体映射方法不作介绍)</li><li>于是图像上每个点的信息都被存储到二维的 texture 中</li><li>每次利用光照模型进行计算的时候根据映射关系,去 <strong>查询</strong> 该点所在 texture 中的信息,对于三角形内部的点同样的也是使用中心坐标插值的方式计算出其在 texture 中的 <span class="math inline">\(u,v\)</span> 坐标</li><li>于是最后就得到了“贴图”模型</li></ol><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002115339.png" alt="image-20221002115339132" style="zoom:80%;" / loading="lazy"></p><h3 id="重心坐标">重心坐标</h3><p>上述讲了很多地方都需要 <strong>插值</strong> 以达到平滑过渡的效果,而插值就需要表示这些坐标</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002112300.png" alt="image-20221002112300920" style="zoom:50%;" / loading="lazy"></p><p>对于三角形所在平面上的任意一点的坐标,都可以用三角形的三个顶点坐标的线性表达式表示 <span class="math display">\[(x, y) = \alpha A + \beta B + \gamma C\]</span> 则 <span class="math inline">\((\alpha ,\beta,\gamma)\)</span> 为该点的 <strong>重心坐标</strong></p><ul><li><p>满足<span class="math inline">\(\alpha +\beta+\gamma = 1\)</span></p></li><li><p>对于三角形内部的点<span class="math inline">\(\alpha ,\beta,\gamma \gt 0\)</span></p></li><li><p>三角形重心的重心坐标为<span class="math inline">\((\frac13,\frac13,\frac13)\)</span></p></li><li><p>几何意义:与三角形的三个顶点构成三个三角形,顶点所对的三角形的面积与三角形总面积的比值,即为对应的重心坐标值</p></li></ul><p>给出其公式: <span class="math display">\[\begin{gathered}\alpha =\frac{-\left(x-x_B\right)\left(y_C-y_B\right)+\left(y-y_B\right)\left(x_C-x_B\right)}{-\left(x_A-x_B\right)\left(y_C-y_B\right)+\left(y_A-y_B\right)\left(x_C-x_B\right)} \\\beta =\frac{-\left(x-x_C\right)\left(y_A-y_C\right)+\left(y-y_C\right)\left(x_A-x_C\right)}{-\left(x_B-x_C\right)\left(y_A-y_C\right)+\left(y_B-y_C\right)\left(x_A-x_C\right)} \\\gamma = 1-\alpha-\beta\end{gathered}\]</span> 重心坐标的意义在于,我们可以将 <span class="math inline">\(A,B,C\)</span> 换成任何顶点的属性,比如法线,漫反射系数,颜色等等以快速方便的进行插值</p><h2 id="纹理走样问题">纹理走样问题</h2><h3 id="纹理太小">纹理太小</h3><p>将低分辨率的纹理应用到高分辨率的画面中,需要将纹理放大(即一个纹理像素 texel 会被映射到多个像素点上),这就导致图片的 <strong>颗粒感</strong> 很严重</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002120831.png" alt="image-20221002120831161" style="zoom:80%;" / loading="lazy"></p><p>具体过程:如图,该片面为纹理平面,红点表示一个像素点映射到纹理平面的位置。因为屏幕的分辨率高于纹理分辨率,那么会存在多个像素映射到一个 <strong>texel</strong> 附近的情况。对于像素来说,他们默认通过 <strong>Nearest</strong> 方法取最近的 <strong>texel</strong>。这显然不是很对,于是乎我们再次选择插值,看看能否令其平滑过渡</p><h4 id="双线性插值">双线性插值</h4><p>双线性插值( bilinear interpolation)是根据距离权重综合周围 <strong>四个点</strong> 的属性进行插值</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002121218.png" alt="image-20221002121218677" style="zoom:80%;" / loading="lazy"></p><p>还是上述的栗子,取 <strong>pixel</strong> 周围四个 <strong>texel</strong>:$ u_{01},u_{11},u_{00},u_{10}$,认为两两 <strong>texel</strong> 之间的距离为单位 1</p><p>然后给出线性插值的公式:<span class="math inline">\(lerp(x,v_0,v_1) = v_0 + x(v_1 -v_0)\)</span></p><p>所谓双线性插值,就像是线性插值做了两轮,一轮水平以及一轮竖直,这里我们以先水平后竖直为例 <span class="math display">\[\begin{aligned}u_0&=\operatorname{lerp}(s, u_{00}, u_{10}) \\u_1&=\operatorname{lerp}(s, u_{01}, u_{11}) \\\\f(x, y) &= \operatorname{lerp}(t, u_{0}, u_{1})\end{aligned}\]</span> 至于双三次插值(bicubic)则是取 16 个像素进行高阶插值,这里不作介绍</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002120535.png" alt="image-20221002120535284" style="zoom:80%;" / loading="lazy"></p><h3 id="纹理太大">纹理太大</h3><p>同样的,当屏幕分辨率小于纹理分辨率时也会出现走样的问题</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002123248.png" alt="image-20221002123248279" style="zoom:80%;" / loading="lazy"></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002123425.png" alt="image-20221002123425170" style="zoom:80%;" / loading="lazy"></p><p>从左至右,一个像素覆盖的纹理范围越来越大,如果此时还是选择用像素中心点查询对应的纹理信息那么就会出现因为采样频率不足导致的走样问题。当然我们可以选择使用超采样(Supersampling)的方法,对多个采样点进行采样。但是对于远处的点这样做的消耗非常之高。</p><h4 id="mipmap">mipmap</h4><p>“采样会引起走样,那如果我们不采样呢?”</p><p>本质上是将点查询问题转换为了范围查询问题,将原本采样一个点,变为直接得到范围内的平均值(最大值/最小值)<del> 我敲,线段树!</del></p><p>具体而言,<strong>多级渐远纹理</strong>(mipmap)能快速地,近似地,范围查询正方形。通过生成一系列的图片达到快速查询的目的。其理念很简单:距观察者的距离超过一定的阈值,OpenGL 会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,它的性能非常好</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002125149.png" alt="image-20221002125148982" style="zoom:80%;" / loading="lazy"></p><ul><li>逐层分辨率减半,即将 4 个相邻像素点求均值合为 1 个像素点</li><li>一共有<span class="math inline">\(log_2n\)</span>层</li><li>其需要占用原本<span class="math inline">\(\frac43\)</span>的存储空间(等比数列求和)</li></ul><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002130326.png" alt="image-20221002130326744" style="zoom:80%;" / loading="lazy"></p><p>那么现在查询一个像素的具体过程:</p><ol type="1"><li><p>同时查询这个像素点和其上方右方的两点</p></li><li><p>取 <span class="math inline">\(L\)</span> 为该点到另外两点距离的最大值(在 <span class="math inline">\(uv\)</span> 平面上) <span class="math display">\[L =\max \left(\sqrt{\left(\frac{d u}{d x}\right)^2+\left(\frac{d v}{d x}\right)^2}, \sqrt{\left(\frac{d u}{d y}\right)^2+\left(\frac{d v}{d y}\right)^2}\right)\]</span></p></li><li><p>根据 <span class="math inline">\(L\)</span> 的值在 <code>mipmap</code> 生成的纹理图片中直接查询 <span class="math inline">\(D = log_2L\)</span>。比如 <span class="math inline">\(L = 4\)</span> 那么对应 <span class="math inline">\(D = log_24 = 2\)</span> 层的纹理,因为原本 <span class="math inline">\(4\times 4\)</span> 的纹理范围在 <span class="math inline">\(D=2\)</span> 中变为 <span class="math inline">\(1\times 1\)</span> 的纹理</p></li></ol><p>显然,这样得到的 <span class="math inline">\(D\)</span> 是只有整数,离散的。因此我们考虑插值。比如我们计算出的 <span class="math inline">\(D = 1.8\)</span>,那么我们先对其在 <span class="math inline">\(D = 1\)</span> 和 <span class="math inline">\(D =2\)</span> 层进行 <strong>双线性插值</strong>,最后层与层之间再进行一次线性插值。也就是 <strong>三线性插值</strong></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002131626.png" alt="image-20221002131626565" style="zoom:80%;" / loading="lazy"></p><p>至此,我们就可以查询任何非整数坐标的纹理</p><p><strong>MipMap 算法的局限</strong>:只能在 u-v 坐标系下做 <strong>方块查询</strong>,有时候会造成 <strong>过度模糊</strong> 的情况,这是因为不同像素点对应到纹理空间的形状并不规整,这一现象在远处更是明显</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002132011.png" alt="image-20221002132011875" style="zoom:67%;" / loading="lazy"></p><p>为了避免这种情况,引入 <strong>各向异性过滤</strong>,在准备不同级别的纹理贴图时,不再是简简单单横纵纹素各减小一半进行分级,而是 <strong>长减半宽不变 or 宽减半长不变 or 长和宽各减半</strong> 三种情况各进行一次分级,显存消耗为原来的三倍,但性能方面并没有多少影响,这种方法就可以实现在 u-v 坐标系下进行矩形查询</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20221002132026.png" alt="image-20221002132026704" style="zoom:80%;" / loading="lazy"></p><h2 id="参考">参考</h2><p><a href="https://zhuanlan.zhihu.com/p/337887394">GAMES101 学习笔记-Shading 概述 - 知乎</a></p><p><a href="https://www.jianshu.com/p/39a5b50a70b3">GAMES101 笔记(3)——Shading - 简书</a></p><p><a href="https://zhuanlan.zhihu.com/p/144332091?utm_source=qq&utm_medium=social&utm_oi=605668290971045888">计算机图形学七:纹理映射(Texture Mapping)及 Mipmap 技术 - 知乎</a></p>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>Shading is the process of applying a material to an object</p></summary>
<category term="Games101笔记" scheme="http://lapras.xyz/categories/Games101%E7%AC%94%E8%AE%B0/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="计算机图形学" scheme="http://lapras.xyz/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9B%BE%E5%BD%A2%E5%AD%A6/"/>
</entry>
<entry>
<title>编译原理(一)--形式语言的基本知识</title>
<link href="http://lapras.xyz/2022/09/29/2017d459.html"/>
<id>http://lapras.xyz/2022/09/29/2017d459.html</id>
<published>2022-09-29T09:51:34.447Z</published>
<updated>2022-09-29T10:03:38.959Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>乔姆斯基将语言形式地定义为由一个 <strong>字母表</strong> 的字母组成的一些串的集合。对于任意一个语言,有一个字母表,可以在字母表上按照一定的形成规则定义一个 <strong>文法</strong>,这个文法所产生的所有句子组成的集合就是这个文法所产生的语言</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929175049.png" alt="image-20220929175049806" style="zoom:67%;" / loading="lazy"></p><span id="more"></span><h2 id="字母表和符号串">字母表和符号串</h2><h3 id="基本概念">基本概念</h3><ul><li>字母表(符号集)是符号的 <strong>有穷非空集合</strong>,通常记为<span class="math inline">\(\Sigma, V\)</span></li><li>字母表中的元素称为 <strong>符号</strong>,符号是字母表中不能再分解的最小单位</li><li>设<span class="math inline">\(\Sigma\)</span>是一个字母表,<span class="math inline">\(\forall x\in \Sigma^*\)</span>,则<span class="math inline">\(x\)</span>称为<span class="math inline">\(\Sigma\)</span>上的一个* *符号串**。符号串是字母表中符号的一个有穷序列</li><li>符号串<span class="math inline">\(s\)</span>的长度,记作<span class="math inline">\(\lvert s \rvert\)</span>,指<span class="math inline">\(s\)</span>中符号的个数</li><li>符号串集合,如果集合<span class="math inline">\(A\)</span>中的所有元素都是字母表<span class="math inline">\(\Sigma\)</span>上的符号串,则称<span class="math inline">\(A\)</span>为字母表<span class="math inline">\(\Sigma\)</span>上定义的符号串集合。也可以称<span class="math inline">\(A\)</span>为字母表<span class="math inline">\(\Sigma\)</span>上定义的某种语言</li></ul><h3 id="字母表基本运算">字母表基本运算</h3><ol type="1"><li><p>字母表乘积(product)</p><p>字母表 <span class="math inline">\(\Sigma_1\)</span> 和字母表 <span class="math inline">\(\Sigma_2\)</span> 相乘: <span class="math display">\[\Sigma_1 \Sigma_2 = \left\{ab\mid a\in \Sigma_1, b\in \Sigma_2\right\}\]</span></p><ul><li>可以用笛卡尔积理解,例如<span class="math inline">\(\{0,1\}\{a, b\}=\{0 a, 0 b, 1 a, 1 b\}\)</span></li></ul></li><li><p>字母表幂运算(power) <span class="math display">\[\left\{\begin{array}{l}\sum^0 =\{\varepsilon\} \\\sum^n =\sum^{n-1} \sum, {n} \geq 1\end{array}\right.\]</span></p><ul><li>字母表的<span class="math inline">\(n\)</span>次幂,即长度为<span class="math inline">\(n\)</span>的 <strong>符号串</strong> 构成的集合</li></ul></li><li><p>字母表正闭包(positive closure) <span class="math display">\[\Sigma^{+}=\Sigma \cup \Sigma^2 \cup \Sigma^3 \cup \ldots\]</span></p><ul><li>实际上就是长度为正数的 <strong>符号串</strong> 构成的集合</li></ul></li><li><p>字母表克林闭包(Kleene closure) <span class="math display">\[\Sigma^{*}=\Sigma ^0 +\Sigma^+=\Sigma ^0 \cup \Sigma \cup \Sigma^2 \cup \Sigma^3 \cup \ldots\]</span></p><ul><li>实际上就是任意长度的 <strong>符号串</strong> 构成的集合</li></ul></li></ol><h3 id="符号串基本运算">符号串基本运算</h3><ol type="1"><li><p>符号串连接(concatenation)</p><p>如果 <span class="math inline">\(x\)</span> 和 <span class="math inline">\(y\)</span> 是符号串,那么 <span class="math inline">\(x\)</span> 和 <span class="math inline">\(y\)</span> 的连接就是把 <span class="math inline">\(y\)</span> 附加到 <span class="math inline">\(x\)</span> 后面而形成的串,记作 <span class="math inline">\(xy\)</span></p><ul><li>值得注意的是:<strong>空串</strong><span class="math inline">\(\varepsilon\)</span>是连接运算的 <strong>单位元</strong>,所以<span class="math inline">\(s\varepsilon = \varepsilon s = s\)</span></li></ul></li><li><p>前缀、后缀和子串</p><p>若 <span class="math inline">\(x,y,z\)</span> 都是 <span class="math inline">\(\Sigma\)</span> 上的符号串,那么 <span class="math inline">\(x\)</span> 被称为 <span class="math inline">\(xy\)</span> 的 <strong>前缀</strong>,<span class="math inline">\(y\)</span> 被称为 <span class="math inline">\(xy\)</span> 的 <strong>后缀</strong>,<span class="math inline">\(y\)</span> 被称为 <span class="math inline">\(xyz\)</span> 的 <strong>子串</strong></p><p>当 <span class="math inline">\(x\)</span> 是 <span class="math inline">\(xy\)</span> 的前缀,且 <span class="math inline">\(x\neq xy\)</span>,则 <span class="math inline">\(x\)</span> 被称为 <span class="math inline">\(xy\)</span> 的真前缀</p><p>当 <span class="math inline">\(y\)</span> 是 <span class="math inline">\(xy\)</span> 的后缀,且 <span class="math inline">\(y\neq xy\)</span>,则 <span class="math inline">\(y\)</span> 被称为 <span class="math inline">\(xy\)</span> 的真后缀</p><p>当 <span class="math inline">\(y\)</span> 是 <span class="math inline">\(xyz\)</span> 的子串,且 <span class="math inline">\(y\neq xyz\)</span>,则 <span class="math inline">\(y\)</span> 被称为 <span class="math inline">\(xyz\)</span> 的真子串</p><p><span class="math inline">\(\varepsilon\)</span> 是任何串的(真)前缀,(真)后缀以及(真)子串</p><p>符号串 <span class="math inline">\(N\)</span>:</p><p>前缀和后缀的数目:1+$N<span class="math inline">\(,真前缀和后缀的数目:\)</span>N$,子串的数目:<span class="math inline">\(1+\frac{\lvert N\rvert+{\lvert N\rvert}^2}{2}\)</span>,真子串的数目:<span class="math inline">\(\frac{\lvert N\rvert+{\lvert N\rvert}^2}{2}\)</span></p></li><li><p>符号串幂运算 <span class="math display">\[\left\{\begin{array}{l}{s}^0 ={\varepsilon} \\{s}^n ={s}^{n-1} s, n \geq 1\end{array}\right.\]</span></p><ul><li>符号串<span class="math inline">\(s\)</span>的<span class="math inline">\(n\)</span>次幂,相当于将<span class="math inline">\(n\)</span>个<span class="math inline">\(s\)</span>连接起来</li></ul></li><li><p>符号串集合连接</p><p>设 <span class="math inline">\(L_1\)</span> 定义在 <span class="math inline">\(\Sigma_1\)</span> 的符号串集合,<span class="math inline">\(L_2\)</span> 定义在 <span class="math inline">\(\Sigma_2\)</span> 的符号串集合:<span class="math inline">\(L_1 L_2 =\left\{xy \mid x \in L_1, y \in L_2 \right\}\)</span></p><ul><li>同样的,也是类似于笛卡尔积的形式</li><li><span class="math inline">\(\Phi L = L \Phi = \Phi\)</span></li><li><span class="math inline">\(\left\{\varepsilon\right\}L = L\left\{\varepsilon\right\} = L\)</span></li></ul></li><li><p>符号串集合幂运算 <span class="math display">\[\left\{\begin{array}{l}{L}^0 =\left\{\varepsilon\right\} \\{L}^n ={L}^{n-1} L, n \geq 1\end{array}\right.\]</span></p></li><li><p>符号串集合的正闭包 <span class="math display">\[L^{+}= L \cup L^2 \cup L^3 \cup \ldots\]</span></p></li><li><p>符号串集合的闭包 <span class="math display">\[L^* = L^0 \cup L^{+}= L \cup L^2 \cup L^3 \cup \ldots\]</span></p><ul><li>两个闭包运算,都与字母表的闭包运算大抵相同</li><li>由于<span class="math inline">\(\Sigma\)</span>本身也可以视作符号串集合,因此将克林闭包<span class="math inline">\(\Sigma^*\)</span>称为* *行集合**,表示字母表中的符号以任意顺序,任意个数,任意长度构成的符号串集合</li></ul></li></ol><h2 id="语言和文法">语言和文法</h2><blockquote><p>句子是由本语言字母表上符号按照一定规则组成的符号串。</p><ul><li>枚举法,如果一个语言仅包含有限条句子,就可以采用枚举法来描述此语言把语言中每条句子都列举出来即可</li><li>自动机识别法,在这种方法中,每种语言对应一种自动机(即某种算法), 由它判定一个符号串是否在该语言中</li><li>文法产生法,这种方法是为每种语言定义一组文法规则,从而产生该语言中的每条句子</li></ul></blockquote><h3 id="巴克斯-诺尔范式">巴克斯-诺尔范式</h3><p>巴科斯范式是描述语法规则一种表示方法,它是由巴科斯为了在 ALGOL60 报告中来描述 ALGOL 语言首先提出的。采用这种形式体系方式定义语法规则,可以用简洁的公式把各种语法规则严格而清晰描述出来。例如,在高级语言中大家所熟知的 <strong>标识符</strong> 这种语法成分,它用巴科斯范式可以描述为:</p><p><标识符> <code>::=</code> <字母> | <标识符> <字母> | <标识符> <数字></p><p><字母> <code>::=</code> a|b|c|…|z</p><p><数字> <code>::=</code> 0|1|2|…|9</p><p>不难发现,巴克斯范式使用的符号如下:</p><ol type="1"><li><code>::=</code>(或 <span class="math inline">\(\to\)</span>),表示 <strong>定义为</strong></li><li><code>|</code>,表示多种不同的选择,不同选择称为 <strong>候选式</strong></li><li><code>< ></code>,表示语法实体,在比较明确的情况下,可省略</li></ol><blockquote><p>比如标识符的定义,就刻画出了其是以 <strong>字母开始(递归定义,总会以字母开始)的</strong> 一串字母和数字任意组合这种特点</p></blockquote><p>产生式:产生式是 <strong>只有一个候选式</strong> 的文法规则,是一个 <strong>非空符号串</strong> 和另一个 <strong>符号串</strong> 的有序偶 <span class="math inline">\((\alpha,\beta)\)</span>,记为 <span class="math inline">\(\alpha::=\beta\)</span> 或 <span class="math inline">\(\alpha\to \beta\)</span>。<span class="math inline">\(\alpha\)</span> 称为产生式的 <strong>左部</strong>,<span class="math inline">\(\beta\)</span> 称为产生式的 <strong>右部</strong>。表示 <span class="math inline">\(\alpha\)</span> 定义为 <span class="math inline">\(\beta\)</span>。对于有相同左部的产生式,可以用 <code>|</code> 简单定义</p><p>字汇表:用于产生式左部和右部中所有符号形成集合为字汇表,记为 <span class="math inline">\(V\)</span></p><p>字汇表的分类:</p><ol type="1"><li><p>非终结符号</p><p>出现在产生式左部,且能 <strong>派生</strong> 出符号或符号串的那些符号称为非终结符,也称语法实体或语法单位,它们的全体构成一个非终结符的集合,记为 <span class="math inline">\(V_N\)</span></p></li><li><p>终结符号</p><p>产生式中不属于 <span class="math inline">\(V\)</span> 的那些符号称为终结符,它们的全体组成终结符的集合,记为 <span class="math inline">\(V_T\)</span>。终结符一般出现在规则的右部</p></li></ol><ul><li>显然,<span class="math inline">\(V = V_N \cup V_T \quad V_N \cap V_T = \Phi\)</span></li></ul><blockquote><p>在上面标识符的定义中,</p><p><span class="math inline">\(V_N\)</span> = {<字母>, <数字>, <标识符>}</p><p><span class="math inline">\(V_T\)</span> = {a, b, c…z,0,1,2…9}</p></blockquote><h3 id="文法">文法</h3><p>文法是规则的 <strong>有穷集合</strong>,形式定义为四元组 <span class="math inline">\(G = (V_N, V_T,P,S)\)</span>,通常记为 <span class="math inline">\(G[S]\)</span></p><ol type="1"><li><span class="math inline">\(V_N\)</span> 是非终结符集合</li><li><span class="math inline">\(V_T\)</span> 是终结符集合</li><li><span class="math inline">\(P\)</span> 代表产生式集</li><li><span class="math inline">\(S\in V_N\)</span> 是文法 <span class="math inline">\(G\)</span> 开始符号,也称识别符号,它至少要在一条产生式左部出现</li></ol><p>栗子: <span class="math display">\[G = (V_N, V_T, P, S)\\V_N = \left\{A, B\right\}\\V_T = \left\{c, d\right\}\\P = \left\{A \to Bc, B \to d\right\}\\S = A\]</span></p><blockquote><p>通常情况下,在对文法的描述时可以省略 <span class="math inline">\(V_N\)</span> 和 <span class="math inline">\(V_T\)</span>,文法的开始符号也可以不需要“显式地”指定,仅需将开始符号写在 G 后的中括号中即可。</p><p>所以上述栗子可以简单描述为:<span class="math inline">\(G[A]:A \to Bc, B \to d\)</span></p></blockquote><p>一些约定:</p><ol type="1"><li>终结符:<span class="math inline">\(a,b,c...\)</span>,<span class="math inline">\(0...9\)</span></li><li>非终结符:<span class="math inline">\(A,B,C...\)</span></li><li>文法符号(终结符或非终结符):<span class="math inline">\(X,Y,Z\)</span></li><li>终结符号串(包括空串):<span class="math inline">\(u,v...z\)</span></li><li>文法符号串(包含空串):<span class="math inline">\(\alpha,\beta...\)</span></li></ol><h3 id="语言">语言</h3><h4 id="推导和规约">推导和规约</h4><p><strong>直接推导和直接归约:</strong> 文法 <span class="math inline">\(G=(V_N,V_T,P,S)\)</span> 有一条产生式 <span class="math inline">\(\alpha\to \beta,\ \alpha\in (V_N\cup V_T)^+,\ \beta\in (V_N\cup V_T)^*\)</span>,假设存在符号串 <span class="math inline">\(x,y\in (V_N\cup V_T)^*\)</span>,使得有符号串 <span class="math inline">\(v\)</span> 和 <span class="math inline">\(w\)</span> 满足 <span class="math inline">\(v=x\alpha y\)</span> 和 <span class="math inline">\(w=x\beta y\)</span>,则称符号串 <span class="math inline">\(v\)</span> <strong>直接推导</strong>(重写)出符号串 <span class="math inline">\(w\)</span>, 符号串 <span class="math inline">\(w\)</span> <strong>直接归约</strong> 到符号串 <span class="math inline">\(v\)</span>,并把符号串 <span class="math inline">\(w\)</span> 叫作符号串 <span class="math inline">\(v\)</span> 的直接派生式,记为 <span class="math display">\[v \Rightarrow w\]</span></p><ul><li>简而言之,就是用产生式的右部替换产生式的左部。特别的,如果<span class="math inline">\(x = y = \varepsilon\)</span>,则对于文法<span class="math inline">\(G\)</span>的任何规则都有<span class="math inline">\(\alpha \Rightarrow \beta\)</span></li></ul><p><strong>推导和归约:</strong> 假设 <span class="math inline">\({u}_0 \in\left({V}_ \cup {V}_\right)^{+}, \ {u}_1, {u}_2, \cdots, {u}_\)</span> 都是 <span class="math inline">\(\left({V}_ \cup {V}_\right)^{*}\)</span> 上定义的符号串,如果存在直接推导序列 <span class="math inline">\({v}={u}_0 \Rightarrow {u}_1 \Rightarrow {u}_2 \Rightarrow \cdots \Rightarrow {u}_={w}({n} \geqslant 1)\)</span>,则称符号串 <span class="math inline">\(v\)</span> 经过 <span class="math inline">\(n\)</span> 步 <strong>推导</strong> 出符号串 <span class="math inline">\(w\)</span>,串 <span class="math inline">\(w\)</span> 经过 <span class="math inline">\(n\)</span> 步 <strong>归约</strong> 到符号串 <span class="math inline">\(v\)</span>,记为 <span class="math display">\[v \Rightarrow^n w\]</span></p><ul><li>显然,当$n = 1 $时就是 <strong>直接推导</strong></li><li><span class="math inline">\(\Rightarrow ^+\)</span>表示经过正数步推导,称为 <strong>推导</strong></li><li><span class="math inline">\(\Rightarrow ^*\)</span>表示经过若干步推导(可以是 0 步),称为* *广义推导**</li><li>推导的步数,直接数<span class="math inline">\(\Rightarrow\)</span>的个数即可</li></ul><blockquote><p>栗子 1:<span class="math inline">\(G[A]:A \to B,\ B \to c\)</span></p><p>解:则称 <span class="math inline">\(A\)</span> 直接推导到 <span class="math inline">\(B\)</span>,<span class="math inline">\(d\)</span> 归约到 <span class="math inline">\(A\)</span></p></blockquote><h4 id="句型和句子">句型和句子</h4><p><strong>句型:</strong> 如果 <span class="math inline">\(S \Rightarrow^* \alpha, \alpha \in\left(V_T \cup V_N\right)^*\)</span>, 则称 <span class="math inline">\(\alpha\)</span> 是 <span class="math inline">\(G\)</span> 的一个句型</p><ul><li>文法<span class="math inline">\(G\)</span>所能产生的 <strong>合法结果</strong> 就是句型</li><li>一个句型中既可以包含终结符<span class="math inline">\(V_T\)</span>,又可以包含非终结符<span class="math inline">\(V_N\)</span>,也可能是空串<span class="math inline">\(\varepsilon\)</span></li></ul><p><strong>句子:</strong> 如果 <span class="math inline">\(S \Rightarrow^* w, w \in V_T^*\)</span>, 则称 <span class="math inline">\(w\)</span> 是 <span class="math inline">\(G\)</span> 的一个句子</p><ul><li>句子是 <strong>只由终结符构成</strong> 的 <strong>句型</strong></li></ul><h4 id="语言-1">语言</h4><p><strong>语言:</strong> 由文法 <span class="math inline">\(G\)</span> 的开始符号 <span class="math inline">\(S\)</span> 推导出的 <strong>所有句子构成的集合</strong> 称为文法 <span class="math inline">\(G\)</span> 生成的 <strong>语言</strong>,记为 <span class="math inline">\(L(G)\)</span>。即 <span class="math display">\[L(G)=\left\{w \mid S \Rightarrow^* w, w \in V_T^*\right\}\]</span></p><ul><li>要使一个文法<span class="math inline">\(G\)</span>能正确描述相应语言<span class="math inline">\(L(G)\)</span>必须保证:<ul><li>由文法<span class="math inline">\(G\)</span>产生的每个句子都在<span class="math inline">\(L(G)\)</span>中</li><li>在语言<span class="math inline">\(L(G)\)</span>中的每个符号串都能由<span class="math inline">\(G\)</span>产生</li></ul></li></ul><blockquote><p>构造下列语言对应的文法</p><p>栗子 1:<span class="math inline">\(L(G) = \left \{0^n1^n \lvert n\ge 0\right \}\)</span></p><p>解:<span class="math inline">\(G[S] = S \to 01,\ S\to 0S1\)</span></p><p>栗子 2:<span class="math inline">\(L(G) = \left \{0^n1^m \lvert n,m\ge 1\right \}\)</span></p><p>解:<span class="math inline">\(G[S] = S \to 0S,\ S \to S1,\ S\to 01\)</span></p></blockquote><p><strong>递归文法:</strong> 像上述栗子中形如 <span class="math inline">\(S\to 0S1\)</span>,这种借助于自己来定义自己的产生式,即在产生式左部和右部具有 <strong>相同的非终结符</strong> 的产生式称为 <strong>递归规则</strong>。如果一个文法中 <strong>至少含有一个递归非终结符</strong>,则将此文法称为 <strong>递归文法</strong></p><ul><li>若有一个规则<span class="math inline">\(U\to ...U...\)</span>则称 <strong>直接递归</strong></li><li>若有规则<span class="math inline">\(U\to U...\)</span>,则称 <strong>直接左递归</strong></li><li>若有规则<span class="math inline">\(U\to ...U\)</span>,则称 <strong>直接右递归</strong></li><li>若有推导式<span class="math inline">\(U\to^+ ...U...\)</span>,则称 <strong>间接递归</strong>,间接递归同样也分为 <strong>间接左递归和间接右递归</strong></li><li>显然,直接递归是间接递归一种特殊情况</li><li>非终结符<span class="math inline">\(U\)</span>称 <strong>递归非终结符</strong></li><li>如果一个语言是无穷的,则描述该语言的文法必定是递归的。他在给 <strong>无限的语言</strong> 以 <strong>有限的表示</strong> 提供了一种可能的方法,但同时也会带来麻烦,比如文法的 <strong>左递归性</strong></li></ul><blockquote><p>栗子:设有文法 <span class="math inline">\(G\)</span> 的规则 <span class="math inline">\(P\)</span> 为 <span class="math display">\[\begin{aligned}&{S}::={Q c} \mid {c} \\&{Q}::={R b} \mid {b} \\&{R}::={S a} \mid {a}\end{aligned}\]</span> 在这 6 条产生式中, 无直接递归规则, 但有如下推导: <span class="math display">\[{Q} \Rightarrow {R b} \Rightarrow {S a b} \Rightarrow {Q c a b}\]</span> 所以 <span class="math inline">\({Q} \Rightarrow^+{Q c a b}\)</span>,因此是间接左递归</p></blockquote><h2 id="句型分析">句型分析</h2><h3 id="短语和句柄">短语和句柄</h3><p>设 <span class="math inline">\(G[Z]\)</span> 是一个文法,<span class="math inline">\(w=x u y\)</span> 是其中某个句型</p><p><strong>短语:</strong> 若 <span class="math inline">\({Z} \Rightarrow{ }^* {xUy}, {U} \in {V}_\)</span> 且 <span class="math inline">\({U} \Rightarrow+{u}, {u} \in {V}^{+}\)</span>,则称 <span class="math inline">\({u}\)</span> 是 <strong>一个相对于非终结符号 <span class="math inline">\({U}\)</span> ,句型 <span class="math inline">\({w}\)</span> 的短语</strong></p><p><strong>简单短语:</strong> 若 <span class="math inline">\({Z} \Rightarrow{ }^* {xUy}\)</span> 且 <span class="math inline">\({U} \Rightarrow {u}\)</span>,则称 <span class="math inline">\({u}\)</span> 是 <strong>一个相对于非终结符号 <span class="math inline">\(U\)</span> ,句型 <span class="math inline">\({w}\)</span> 的简单短语</strong></p><ul><li>不难发现,短语的概念是要 <strong>处于某个句型</strong> 以及相对于一个 <strong>非终结符</strong>,即 <strong>推导</strong> 该短语的 <strong>非终结符</strong> 以及该短语所在的 <strong>句型</strong></li><li>根据推导的次数判定是简单短语还是短语</li></ul><p><strong>句柄:</strong> 一个句型 <strong>最左边</strong> 的简单短语(最左简单短语)称为该句型的句柄(或柄短语),句柄最左边的符号称 <strong>句柄的头</strong>,句柄最右边的符号称 <strong>句柄的尾</strong></p><blockquote><p>栗子:设有文法 ${G}[{S}]=(<br /></p></blockquote>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>乔姆斯基将语言形式地定义为由一个 <strong>字母表</strong> 的字母组成的一些串的集合。对于任意一个语言,有一个字母表,可以在字母表上按照一定的形成规则定义一个 <strong>文法</strong>,这个文法所产生的所有句子组成的集合就是这个文法所产生的语言</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220929175049.png" alt="image-20220929175049806" style="zoom:67%;" /></p></summary>
<category term="编译原理" scheme="http://lapras.xyz/categories/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="形式语言" scheme="http://lapras.xyz/tags/%E5%BD%A2%E5%BC%8F%E8%AF%AD%E8%A8%80/"/>
</entry>
<entry>
<title>Games101-Rasterization</title>
<link href="http://lapras.xyz/2022/09/25/154ddc25.html"/>
<id>http://lapras.xyz/2022/09/25/154ddc25.html</id>
<published>2022-09-25T15:49:11.297Z</published>
<updated>2022-09-26T07:13:50.901Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>在 <a href="https://lapras.xyz/2022/09/20/918c829e.html">上文-Transform</a> 中,我们已经将常见经过 MVP(Model-View-Projection)变换,使其转换到了二维平面。但终究还是要令其在“屏幕”上成像的,这也是 Rasterization 光栅化要做的事</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925223836.png" alt="image-20220925223836691" style="zoom: 67%;" / loading="lazy"></p><span id="more"></span><h3 id="如何成像">如何成像?</h3><p>首先让我们定义一下 <strong>屏幕</strong>:一个大小为其 <strong>分辨率</strong> 的 <strong>二维数组</strong>,数组中的每个元素称之为 <strong>像素</strong>(Pixel,Picture element)。那么所谓光栅化(Rasterize)就是指把东西画在屏幕上的过程(Drawing onto the screen)</p><blockquote><p>具体像素定义(针对本节课,比较简略):</p><ol type="1"><li>是一个独立的小正方形</li><li>颜色由 <code>RGB</code> 定义</li><li>是成像的最小单位</li></ol></blockquote><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925222219.png" alt="image-20220925222212092" style="zoom:80%;" / loading="lazy"></p><p>定义 <strong>屏幕空间</strong>:</p><ul><li><p>以左下角为原点<span class="math inline">\((0,0)\)</span>,每个单位长度是一个像素的边长,规定坐标只能为整数</p></li><li><p>显然像素<span class="math inline">\((x, y)\)</span>的中心位于<span class="math inline">\((x+0.5, y+0.5)\)</span></p></li><li><p>像素坐标的范围从<span class="math inline">\((0,0)\sim (width-1, height-1)\)</span></p></li></ul><p>那么就要把我们之前做好的 <strong>标准正方体</strong> 变换到这个屏幕空间中</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925222803.png" alt="image-20220925222803251" style="zoom:80%;" / loading="lazy"></p><p>首先,请忽视 <span class="math inline">\(Z\)</span> 的存在(另有他用),只考虑 <span class="math inline">\(X-Y\)</span>,很容易写出变换矩阵 <span class="math display">\[M_{ {viewport }}=\left[\begin{array}{cccc}\frac{ { width }}{2} & 0 & 0 & \frac{ { width }}{2} \\0 & \frac{2} & 0 & \frac{ { height }}{2} \\0 & 0 & 1 & 0 \\0 & 0 & 0 & 1\end{array}\right]\]</span> 然后再把屏幕空间中的多边形分解为 <strong>三角形</strong>,成像到屏幕上,光栅化的流程基本就走完了</p><h3 id="三角形三角形">三角形?三角形!</h3><ol type="1"><li><p>三角形是最基本的图元,任何多边形都可以分解成三角形</p></li><li><p>三角形上所有的点一定是共面的</p></li><li><p>可以通过向量叉积判断内外</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925224523.png" alt="image-20220925224523534" style="zoom:80%;" / loading="lazy"></p><p>观察这个三角形 <span class="math inline">\(P_0P_1P_2\)</span>,要判断 <span class="math inline">\(Q\)</span> 点与三角形的位置关系,只需要计算 $ $,这样就可以根据叉乘结果的正负判断 <span class="math inline">\(Q\)</span> 在 <span class="math inline">\(\overrightarrow{P_0P_1}\)</span> 的左边还是右边,接着在计算 <span class="math inline">\(\overrightarrow{P_1P_2} \times \overrightarrow{P_1Q}\)</span> 以及 <span class="math inline">\(\overrightarrow{P_2P_3} \times \overrightarrow{P_2Q}\)</span> 就可以很容易得到结果啦!</p></li><li><p>具有成熟的顶点插值方法</p></li></ol><p>那么如何把三角形进行光栅化呢?先来个最简单的办法:<strong>采样</strong>,跟信号与系统的概念类似,本质是将连续的信息离散化,这里我们用三角形的 <strong>三个顶点坐标</strong> 提供信息,依次判断每个 <strong>像素点中心</strong> 是否在三角形 <strong>内部</strong>,是的话就点亮这个像素点</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925223836.png" alt="image-20220925223836691" style="zoom:80%;" / loading="lazy"></p><p>用一段简单的程序描述,其中 <code>isinside</code> 就是三角形叉积判断内外:</p><pre class="language-c++" data-language="c++"><code class="language-c++">bool isinside(Triangle tri, float x, float y){...}for(int i = 0; i < imax; i++){ for(int j = 0; j < jmax; j++){ image[i][j] = isinside(tri, i+0.5, j+0.5); }}</code></pre><p>每次遍历全部像素显然比较浪费时间,所以可以用 <strong>Axis-Aligned Bounding Box(AABB)</strong> 来进行优化,即先根据三个顶点的坐标确定一个范围的正方形后再遍历。以及适合细长三角形的 <strong>Incremental Triangle Traversal</strong></p><h3 id="我超锯齿">我超,锯齿!</h3><p>OK,万事俱备,点亮屏幕!</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925225750.png" alt="image-20220925225750876" style="zoom:80%;" / loading="lazy"></p><p>显然,这种结果并非我们所愿,为什么会这样的?是我们的采样方法过于简单了吗?</p><h4 id="采样理论">采样理论</h4><p>在图形学中,采样无处不在。如光栅化相当于对屏幕中的二维点进行采样、录像相当于对时间进行采样。在采样过程中,当出现一些不正确的现象时,我们会把这些现象称为走样(Aliasing),比如:</p><table><thead><tr class="header"><th>现象</th><th>示例</th></tr></thead><tbody><tr class="odd"><td>锯齿(Jaggies )</td><td><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925230315.png" alt="image-20220925230315513" style="zoom: 25%;" / loading="lazy"></td></tr><tr class="even"><td>摩尔纹(Moir é Patterns)</td><td><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925230359.png" alt="image-20220925230359098" style="zoom: 25%;" / loading="lazy"></td></tr><tr class="odd"><td>车轮现象(Wagon Wheel Illusion)</td><td><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925230454.png" alt="image-20220925230454399" style="zoom: 25%;" / loading="lazy"></td></tr></tbody></table><p>背后的原理其实是 <strong>傅里叶变换</strong> 和 <strong>奈奎斯特采样定律</strong></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925231351.png" alt="image-20220925231351667" style="zoom: 67%;" / loading="lazy"></p><p>如图,有 5 个连续的函数 <span class="math inline">\(f_1 \sim f_5\)</span>,其频率渐渐变高。竖直的虚线表示 <strong>采样频率</strong>。在相同采样频率下,我们对这些函数进行采样,然后把采样后的点连线,不难发现</p><ul><li>频率越低的函数,采样后的连线与原函数差异越小</li><li>频率越高的函数,采样后的连线与原函数差异越大</li></ul><p>这就是 <strong>走样</strong> 的产生原因,因为信息在采样的过程中丢失了,不足以令我们 <strong>还原</strong> 出“完整”的信息。那么根据原因不难想到两种方法以抗锯齿:</p><ul><li><strong>提高采样率</strong>:换一块分辨率更高的屏幕</li><li><strong>反走样</strong></li></ul><h4 id="反走样">反走样</h4><p>回过头来思考,为什么会产生“锯齿感”,换言之就是不够“平滑”。这里的平滑其实指的就是边缘的渐变效果,颜色和颜色之间不是非黑即白而是逐渐过渡的感觉。这也引出了我们反走样(Antialiased Sampling)的第一个思路:<strong>边界模糊</strong></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925231024.png" alt="image-20220925231024236" style="zoom: 67%;" / loading="lazy"></p><p>简言之,就是先 <strong>模糊</strong>,再 <strong>采样</strong>。对于图片而言,也可以进行二维的傅立叶分解。在频率上,这种操作相当于先用 <strong>低通滤波</strong> 把 <strong>高频信息</strong> 过滤掉,再进行采样。</p><p>等等你说你不知道啥是低通滤波,啥是高频信息?来看些栗子</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925232459.png" alt="image-20220925232459184" style="zoom:80%;" / loading="lazy"></p><p>右边的图像就是其傅里叶图像,你不需要直到其具体含义。象征性地记住越靠近中心的部分表示其频率越低即可。这里我使用 <code>Python</code> 的 <code>Numpy</code> 和 <code>opencv</code> 库进行了操作,首先将原图的傅里叶信息表示出来,然后对其傅里叶信息进行操作,也就是各种 <strong>滤波</strong>。那么不难发现,高通滤波(HighPassFiltering )也就是屏蔽低频率的图像,留下高频率的图像,会让图片的 <strong>边界</strong> 保留下来。反之低通滤波(LowPassFiltering )会让 <strong>边界模糊</strong>。中通滤波则是兼而有之。那么我们可以得到:对于图片而言,其高频信号反映了其 <strong>边界特征</strong>(剧烈变化)。当然,上述的做法都非常的 <strong>暴力</strong>,具体的滤波器有非常多(比如 <strong>卷积</strong>,数字图片处理懂得伐),这里只是举个例子。</p><p>除了模糊化之外,还有很多方案可以用来抗锯齿</p><ul><li><p>MSAA(muti-sample anti-aliasing):每个像素多次采样,求平均,像素的颜色值为负责的区域内取样多次颜色值的平均</p></li><li><p>FXAA (Fast Approximate AA):用边界寻找算法找到边界,直接换成没有锯齿的边界</p></li><li><p>TAA (Tem'poral AA) :时序信息,借助前面帧的信息来进行采样</p></li><li><p>DLSS (Deep Learning Super Sampling) :深度学习的方式还原高分辨率图片</p></li></ul><h3 id="场景光栅化">场景光栅化</h3><p>光栅化完三角形,接着就要把之前的场景全部光栅化(还记得大明湖畔的 <span class="math inline">\(Z\)</span> 吗),为了解决近处物体遮蔽远处物体的问题,最简单的思路就是先 <strong>画</strong> 远得,再画近的。但这显然有些问题,因为你将每个三角形看成一个整体,但有些时候他们的远近并不好 <strong>分辨</strong>(考虑三个三角形头尾互相叠)。所以换个角度,从像素出发,反之最后都要光栅化,直接决定每个像素应该画什么不是更简单吗?这也就是 <strong>深度缓冲</strong>(Z-Buffer)的思想:</p><p>工作原理:每次渲染的时候除了生成最终的图像之外,还生成一张 <strong>深度图</strong>,该深度图记录了每个像素当前的最小深度</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925234817.png" alt="image-20220925234817411" style="zoom:80%;" / loading="lazy"></p><p>用代码更好解释:</p><pre class="language-c++" data-language="c++"><code class="language-c++">for (each triangle T)//遍历所有三角形 for (each sample(x,y,z) in T)//遍历该三角形涉及到的所有像素 if (z < zbuffer[x,y]) {//该像素的当前深度比之前的深度小(距离摄像机更近) framebuffer[x,y] = rgb;//更新该像素的颜色 zbuffer[x,y] = z;//更新该像素的深度 }</code></pre><ul><li>显然,这是个线性的复杂度<span class="math inline">\(O(n)\)</span>,<span class="math inline">\(n\)</span>为三角形的个数</li><li>但是其无法处理透明物体</li></ul><h2 id="作业-2">作业 2</h2><blockquote><p>在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣。所以这一次我们继续推进一步——在屏幕上画出一个实心三角形,换言之,栅格化一个三角形。上一次作业中,在视口变化之后,我们调用了函数 rasterize_wireframe(const Triangle& t)。但这一次,你需要自己填写并调用函数 rasterize_triangle(const Triangle& t)。该函数的内部工作流程如下: 1. 创建三角形的 2 维 bounding box。 2. 遍历此 bounding box 内的所有像素(使用其整数索引)。然后,使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。 3. 如果在内部,则将其位置处的插值深度值(interpolated depth value) 与深度缓冲区(depth buffer) 中的相应值进行比较。</p><ol start="4" type="1"><li>如果当前点更靠近相机,请设置像素颜色并更新深度缓冲区(depth buffer)。你需要修改的函数如下:</li></ol><p>• <code>rasterize_triangle()</code>: 执行三角形栅格化算法</p><p>• <code>static bool insideTriangle()</code> :测试点是否在三角形内</p><p>因为我们只知道三角形三个顶点处的深度值,所以对于三角形内部的像素,我们需要用插值的方法得到其深度值。我们已经为你处理好了这一部分,因为有关这方面的内容尚未在课程中涉及。插值的深度值被储存在变量 z_interpolated 中。</p></blockquote><p>代码如下:</p><p><code>main.cpp</code>:</p><pre class="language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">constexpr</span> <span class="token keyword">double</span> MY_PI <span class="token operator">=</span> <span class="token number">3.1415926</span><span class="token punctuation">;</span><span class="token keyword">inline</span> <span class="token keyword">double</span> <span class="token function">deg2red</span><span class="token punctuation">(</span><span class="token keyword">double</span> deg<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> deg <span class="token operator">*</span> MY_PI <span class="token operator">/</span> <span class="token number">180</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token keyword">inline</span> <span class="token keyword">double</span> <span class="token function">cot</span><span class="token punctuation">(</span><span class="token keyword">double</span> X<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token number">1.0</span> <span class="token operator">/</span> <span class="token function">tan</span><span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f <span class="token function">get_projection_matrix</span><span class="token punctuation">(</span><span class="token keyword">float</span> eye_fov<span class="token punctuation">,</span> <span class="token keyword">float</span> aspect_ratio<span class="token punctuation">,</span> <span class="token keyword">float</span> zNear<span class="token punctuation">,</span> <span class="token keyword">float</span> zFar<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// TODO: Copy-paste your implementation from the previous assignment.</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f projection<span class="token punctuation">;</span>eye_fov <span class="token operator">=</span> <span class="token function">deg2red</span><span class="token punctuation">(</span>eye_fov <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//注意这里弧度转角度的时候为其一半</span>projection <span class="token operator"><<</span><span class="token function">cot</span><span class="token punctuation">(</span>eye_fov<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">*</span> aspect_ratio <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token function">cot</span><span class="token punctuation">(</span>eye_fov<span class="token punctuation">)</span><span class="token operator">/</span><span class="token number">2</span> <span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> zNear <span class="token operator">+</span> zFar <span class="token operator">/</span> <span class="token punctuation">(</span>zNear <span class="token operator">-</span> zFar<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">2.0</span> <span class="token operator">*</span> zNear <span class="token operator">*</span> zFar <span class="token operator">/</span> <span class="token punctuation">(</span>zNear <span class="token operator">-</span> zFar<span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token keyword">return</span> projection<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p><code>rasterize.cpp</code>:</p><pre class="language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">static</span> <span class="token keyword">bool</span> <span class="token function">insideTriangle</span><span class="token punctuation">(</span><span class="token keyword">int</span> x<span class="token punctuation">,</span> <span class="token keyword">int</span> y<span class="token punctuation">,</span> <span class="token keyword">const</span> Vector3f<span class="token operator">*</span> _v<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]</span>Vector3f <span class="token function">P</span><span class="token punctuation">(</span>x <span class="token operator">+</span> <span class="token number">0.5f</span><span class="token punctuation">,</span> y <span class="token operator">+</span> <span class="token number">0.5f</span><span class="token punctuation">,</span> <span class="token number">1.0f</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> Vector3f<span class="token operator">&</span> A <span class="token operator">=</span> _v<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">const</span> Vector3f<span class="token operator">&</span> B <span class="token operator">=</span> _v<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">const</span> Vector3f<span class="token operator">&</span> C <span class="token operator">=</span> _v<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>Vector3f AB <span class="token operator">=</span> B <span class="token operator">-</span> A<span class="token punctuation">;</span>Vector3f BC <span class="token operator">=</span> C <span class="token operator">-</span> B<span class="token punctuation">;</span>Vector3f CA <span class="token operator">=</span> A <span class="token operator">-</span> C<span class="token punctuation">;</span>Vector3f AP <span class="token operator">=</span> P <span class="token operator">-</span> A<span class="token punctuation">;</span>Vector3f BP <span class="token operator">=</span> P <span class="token operator">-</span> B<span class="token punctuation">;</span>Vector3f CP <span class="token operator">=</span> P <span class="token operator">-</span> C<span class="token punctuation">;</span><span class="token keyword">float</span> z1 <span class="token operator">=</span> AB<span class="token punctuation">.</span><span class="token function">cross</span><span class="token punctuation">(</span>AP<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">z</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">float</span> z2 <span class="token operator">=</span> BC<span class="token punctuation">.</span><span class="token function">cross</span><span class="token punctuation">(</span>BP<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">z</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">float</span> z3 <span class="token operator">=</span> CA<span class="token punctuation">.</span><span class="token function">cross</span><span class="token punctuation">(</span>CP<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">z</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> <span class="token punctuation">(</span>z1 <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&&</span> z2 <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&&</span> z3 <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token punctuation">(</span>z1 <span class="token operator"><</span> <span class="token number">0</span> <span class="token operator">&&</span> z2 <span class="token operator"><</span> <span class="token number">0</span> <span class="token operator">&&</span> z3 <span class="token operator"><</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">void</span> rst<span class="token double-colon punctuation">::</span>rasterizer<span class="token double-colon punctuation">::</span><span class="token function">rasterize_triangle</span><span class="token punctuation">(</span><span class="token keyword">const</span> Triangle<span class="token operator">&</span> t<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">auto</span> v <span class="token operator">=</span> t<span class="token punctuation">.</span><span class="token function">toVector4</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//将3D坐标转化为4D齐次坐标</span><span class="token comment">//TODO : Find out the bounding box of current triangle.</span><span class="token function">puts</span><span class="token punctuation">(</span><span class="token string">"坐标为"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>std<span class="token double-colon punctuation">::</span>cout <span class="token operator"><<</span> <span class="token string">"v[0]:"</span> <span class="token operator"><<</span> std<span class="token double-colon punctuation">::</span>endl <span class="token operator"><<</span> v<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator"><<</span> std<span class="token double-colon punctuation">::</span>endl<span class="token operator"><<</span> <span class="token string">"v[1]:"</span> <span class="token operator"><<</span> std<span class="token double-colon punctuation">::</span>endl <span class="token operator"><<</span> v<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator"><<</span> std<span class="token double-colon punctuation">::</span>endl<span class="token operator"><<</span> <span class="token string">"v[2]:"</span> <span class="token operator"><<</span> std<span class="token double-colon punctuation">::</span>endl <span class="token operator"><<</span> v<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator"><<</span> std<span class="token double-colon punctuation">::</span>endl<span class="token punctuation">;</span><span class="token keyword">float</span> x_min <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token function">min</span><span class="token punctuation">(</span>v<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span><span class="token function">min</span><span class="token punctuation">(</span>v<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> v<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">float</span> x_max <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token function">max</span><span class="token punctuation">(</span>v<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span><span class="token function">max</span><span class="token punctuation">(</span>v<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> v<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">float</span> y_min <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token function">min</span><span class="token punctuation">(</span>v<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span><span class="token function">min</span><span class="token punctuation">(</span>v<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> v<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">float</span> y_max <span class="token operator">=</span> std<span class="token double-colon punctuation">::</span><span class="token function">max</span><span class="token punctuation">(</span>v<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> std<span class="token double-colon punctuation">::</span><span class="token function">max</span><span class="token punctuation">(</span>v<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> v<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//将坐标向上取整和向下取整保证三角形位于box内</span>x_min <span class="token operator">=</span> <span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator"><</span><span class="token keyword">int</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span><span class="token function">floor</span><span class="token punctuation">(</span>x_min<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>y_min <span class="token operator">=</span> <span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator"><</span><span class="token keyword">int</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span><span class="token function">floor</span><span class="token punctuation">(</span>y_min<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>x_max <span class="token operator">=</span> <span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator"><</span><span class="token keyword">int</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span><span class="token function">ceil</span><span class="token punctuation">(</span>x_max<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>y_max <span class="token operator">=</span> <span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator"><</span><span class="token keyword">int</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>std<span class="token double-colon punctuation">::</span><span class="token function">ceil</span><span class="token punctuation">(</span>y_max<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">puts</span><span class="token punctuation">(</span><span class="token string">"Bounding Box为"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//按照[min_x, max_x],[min_y, max_y]输出</span><span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"x:[%0f, %0f], y:[%0f, %0f]\n"</span><span class="token punctuation">,</span> x_min<span class="token punctuation">,</span> x_max<span class="token punctuation">,</span> y_min<span class="token punctuation">,</span> y_max<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//float x_min, x_max, y_min, y_max;</span><span class="token comment">// iterate through the pixel and find if the current pixel is inside the triangle</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> x_min<span class="token punctuation">;</span> i <span class="token operator"><=</span> x_max<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> y_min<span class="token punctuation">;</span> j <span class="token operator"><=</span> y_max<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">insideTriangle</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> j<span class="token punctuation">,</span> t<span class="token punctuation">.</span>v<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">float</span> min_depth <span class="token operator">=</span> FLT_MAX<span class="token punctuation">;</span><span class="token comment">//设置默认深度为无穷大(远)</span><span class="token comment">// If so, use the following code to get the interpolated z value.</span><span class="token comment">//C++17标准,使用std::tuple返回多个值</span><span class="token comment">//重心坐标计算</span><span class="token keyword">auto</span> <span class="token punctuation">[</span>alpha<span class="token punctuation">,</span> beta<span class="token punctuation">,</span> gamma<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">computeBarycentric2D</span><span class="token punctuation">(</span><span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator"><</span><span class="token keyword">float</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token generic-function"><span class="token function">static_cast</span><span class="token generic class-name"><span class="token operator"><</span><span class="token keyword">float</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>j <span class="token operator">+</span> <span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">,</span> t<span class="token punctuation">.</span>v<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">float</span> w_reciprocal <span class="token operator">=</span> <span class="token number">1.0</span> <span class="token operator">/</span> <span class="token punctuation">(</span>alpha <span class="token operator">/</span> v<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">w</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> beta <span class="token operator">/</span> v<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">w</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> gamma <span class="token operator">/</span> v<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">w</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">float</span> z_interpolated <span class="token operator">=</span> alpha <span class="token operator">*</span> v<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">z</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">/</span> v<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">w</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> beta <span class="token operator">*</span> v<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">z</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">/</span> v<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">w</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> gamma <span class="token operator">*</span> v<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">z</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">/</span> v<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">w</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>z_interpolated <span class="token operator">*=</span> w_reciprocal<span class="token punctuation">;</span><span class="token comment">//获取像素在缓冲区中的索引</span><span class="token keyword">int</span> buf_index <span class="token operator">=</span> <span class="token function">get_index</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> j<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//如果当前深度小则更新,否则跳过</span><span class="token keyword">if</span> <span class="token punctuation">(</span>z_interpolated <span class="token operator">>=</span> depth_buf<span class="token punctuation">[</span>buf_index<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span><span class="token comment">//更新深度</span>depth_buf<span class="token punctuation">[</span>buf_index<span class="token punctuation">]</span> <span class="token operator">=</span> z_interpolated<span class="token punctuation">;</span><span class="token comment">//绘制像素</span><span class="token function">set_pixel</span><span class="token punctuation">(</span><span class="token function">Vector3f</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> j<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> t<span class="token punctuation">.</span><span class="token function">getColor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>效果:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220926150834.png" alt="image-20220926150834772" style="zoom: 67%;" / loading="lazy"></p><h2 id="参考">参考</h2><p><a href="https://scarletsky.github.io/2020/06/10/games101-notes-rasterization/">Games101 笔记 —— 光栅化 - scarletsky</a></p><p><a href="https://pangruitao.com/post/2339#51_%E4%BB%8B%E7%BB%8D%E6%88%90%E5%83%8F">GAMES101(5-9): 光栅化-学习笔记 – PP's Blog</a></p><p><a href="https://aitechtogether.com/article/8460.html">计算机视觉系列教程 2-2:详解图像滤波算法 | AI 技术聚合</a></p><p><a href="https://github.com/Quanwei1992/GAMES101">Quanwei1992/GAMES101: GAMES101: 现代计算机图形学入门 作业</a></p>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>在 <a href="https://lapras.xyz/2022/09/20/918c829e.html">上文-Transform</a> 中,我们已经将常见经过 MVP(Model-View-Projection)变换,使其转换到了二维平面。但终究还是要令其在“屏幕”上成像的,这也是 Rasterization 光栅化要做的事</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220925223836.png" alt="image-20220925223836691" style="zoom: 67%;" /></p></summary>
<category term="Games101笔记" scheme="http://lapras.xyz/categories/Games101%E7%AC%94%E8%AE%B0/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="计算机图形学" scheme="http://lapras.xyz/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9B%BE%E5%BD%A2%E5%AD%A6/"/>
</entry>
<entry>
<title>汇编语言程序设计(二)</title>
<link href="http://lapras.xyz/2022/09/22/cdaa62af.html"/>
<id>http://lapras.xyz/2022/09/22/cdaa62af.html</id>
<published>2022-09-22T02:55:43.628Z</published>
<updated>2022-09-22T02:56:31.703Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>太吾绘卷正式版出了!准备小溜一手!再次推荐王爽老师的《汇编语言》</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220922105447.png" alt="image-20220922105447471" style="zoom:67%;" / loading="lazy"></p><span id="more"></span><h2 id="指令概述">指令概述</h2><blockquote><p>指令是 CPU 操作的基本单位,每条指令执行一个特定的操作。CPU 全部指令的集合,称为指令集。</p></blockquote><ul><li>机器指令:二进制格式编码的序列(一串 0,1 代码书写)注意:硬件只能识别,存储,运行机器指令</li><li>符号指令:用字符串形式的序列(包含字符串形式的操作码以及操作数助记符)表示</li></ul><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220908094246.png" alt="image-20220908094238953" style="zoom:80%;" / loading="lazy"></p><ul><li><p>操作码 :计算机要执行的操作,如:加、减、逻辑与等</p></li><li><p>操作数 : 执行操作过程所要操作的数,如加运算的两个加数</p></li><li><p>标号:标号表示该条指令的 <strong>符号地址</strong>。当该条指令被作为分支或循环等指令的转移目标或作为程序开始执行的首条语句时,需要设置标号,其他情况下则可以忽略,其命名必须以字母开头,与操作码用“:”分隔</p></li><li><p>注解:以“ ;”开头,不执行</p></li></ul><p>指令的长度:指令在存储器中占用的 <strong>字节数</strong> 称为指令长度。 80X86 指令长度(机器指令长度)为 <span class="math inline">\(1\sim 16\)</span> 字节</p><p>指令的地址:多字节指令占用连续的内存单元,存放指令第一字节的内存单元地址,称为 <strong>指令地址</strong></p><p>指令的存放:首先存放操作码,然后存放操作数。多字节操作数连续存放,顺序依据 <strong>小端规则</strong>(Little Endian)即 : 低位字节存放在低地址单元,高位字节存放在相邻的高地址单元</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220908095202.png" alt="image-20220908095202646" style="zoom:80%;" / loading="lazy"></p><h4 id="标志寄存器">标志寄存器</h4><blockquote><p>指令的操作对象除了数据外还包括状态。在大多数情况下,使用标志寄存器中的 <strong>标志位</strong> 来存储状态。标志位分为两种类型:<strong>状态标志</strong> 和 <strong>控制标志</strong>。状态标志用于作为某些指令操作的 <strong>前提状态</strong> 以及指令操作完成后的 <strong>结果状态</strong>。控制标志可以 <strong>设定 CPU 的某些功能</strong>,例如中断;或者设定指令的操作功能,例如串操作指令。控制标志值可以用相应指令进行设定。</p></blockquote><p>80486 作为 32 位微处理器,其有 32 位的 CPU 标志寄存器,但实际只使用 15 位</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220908095454.png" alt="image-20220908095454647" style="zoom: 67%;" / loading="lazy"></p><h3 id="状态标志">状态标志</h3><ol type="1"><li><p><code>C(Carry Flag)</code> 标志:进位</p><p>字节/字/双字加减,最高位产生进位。<code>C</code> 标志置 1,否则置 0</p><p>除此之外,无符号数乘法指令、求补指令、移位指令和一部分逻辑运算指令在执行后对 <code>C</code> 标志也会产生影响</p></li><li><p><code>A(Auxitiary Flag)</code> 标志:辅助进位</p><p>字节/字/双字加减,<code>D3</code> 向 <code>D4</code> 产生进位。<code>A</code> 标志置 1,否则置 0</p><blockquote><p>从 0 开始数!!!</p></blockquote></li><li><p><code>S(Sign Flag)</code> 标志:符号标志</p><p>字节/字/双字运算,结果的最高位为 1 时。<code>S</code> 标志置 1,否则置 0</p><blockquote><p>这里的最高位,有符号数里是符号位,无符号数是所谓的 <span class="math inline">\(D_{15},D_{7}\)</span>,而非进位产生的位</p></blockquote></li><li><p><code>Z(Zero Flag)</code> 标志:零标志</p><p>运算结果为全 0 时,<code>Z</code> 标志置 1,否则置 0</p></li><li><p><code>P(Parity Flag)</code> 标志:奇偶标志</p><p>结果低 8 位中,<span class="math inline">\(1\)</span> 的个数为偶数,则 <code>P</code> 标志置 1,否则置 0</p></li><li><p><code>O(Overflow Flag)</code> 标志:溢出标志</p><p>则加数与被加数的最高位相同,却与结果的最高位相异,则 <code>O</code> 标志置 1,否则置 0</p><blockquote><p>有符号数运算,判 o 标志,O 标志为 1, 有溢出。 无符号数加/减,判 C 标志,C 标志为 1, 有溢出。</p></blockquote></li></ol><h3 id="控制标志">控制标志</h3><ol type="1"><li><p><code>D(Direction Flag)</code> 标志:方向标志</p><p><code>D</code> 标志用于在串操作指令中控制字符申指针的调整方向,<code>D</code> = 0 时,为增址型调整,即指针由低位地址向高位地址移动;<code>D</code> = 1 时,为减址型调整,即指针由高位地址向低地址移动。在执行串操作指令前,使用处理机控制指令 CLD 将 <code>D</code> 标志设置为 0,或使用 STD 将 <code>D</code> 标志设置为 1</p></li><li><p><code>I(Interrupt—enable Flag)</code> 标志:中断允许标志</p><p><code>I</code> 标志用于控制 CPU 是否响应来自引脚 INTR 的可屏蔽中断请求。<code>I</code> 标志为 0 时,CPU 不响应可屏蔽中断请求;<code>I</code> 标志为 1 时,CPU 响应可 <strong>屏蔽中断请求</strong>。使用处理机控制指令 CLI 和 STI 设置 <code>I</code> 标志,CLI 将 <code>I</code> 标志设置为 0,STI 将 <code>I</code> 标志设置为 1</p></li><li><p><code>T(Tracc Flag)</code> 标志:陷阱(追踪)标志</p><p><code>T</code> 标志用于控制 CPU 是否以 <strong>单步方式</strong> 执行指令。<code>T</code> 标志为 0,CPU 以连续方式执行指令;<code>T</code> 标志为 1,CPU 以单步方式执行指令,即每执行一条指令后产生一次单步中断,自动调用中断类型号为 1 的单步中断服务子程序。<code>T</code> 标志默认值为 0,需要启动单步操作时,可以通过逻辑运算指令将标志寄存器中的 T 标志位设置为 1</p></li></ol><h2 id="寻址方式">寻址方式</h2><blockquote><p> 操作数是指令的操作对象,寻址方式就是在指令中,<strong>使用特定的助记符</strong>(地址表达式),告知 CPU 如何计算出操作数的地址,从而正确地取出操作数进行后继的指令操作。</p></blockquote><ol type="1"><li>操作数包含在 <strong>指令</strong> 中,这种操作数称为 <strong>立即数</strong></li><li>操作数存放在 CPU 的某个 <strong>寄存器</strong> 中,这种操作数称为 <strong>寄存器操作数</strong></li><li>操作数存放在 <strong>存储器</strong> 中,这种操作数称为 <strong>存储器操作数</strong></li><li>操作数存放在 <strong>I/O 端口</strong> 中,这种操作数称为 <strong>I/O 端口操作数</strong></li></ol><h3 id="立即寻址方式">立即寻址方式</h3><p> 立即寻址方式所提供的操作数直接放在指令中,紧跟在操作码的后面,与操作码一起放在代码段区域中</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220908102207.png" alt="image-20220908102207431" style="zoom:67%;" / loading="lazy"></p><ul><li><p>立即寻址的特点就在于其操作数是 <strong>立即数</strong></p><blockquote><p>所谓 <strong>立即数</strong>,包括常用的 2 进制(后缀 B),8 进制(后缀 Q),16 进制数(后缀 H,若前缀是字母则需补前缀 0),带符号的真值,带单引号的字符(ASCII),简单的算术表达式</p></blockquote></li></ul><h3 id="寄存器操作数寻址">寄存器操作数寻址</h3><p>寄存器寻址即将操作数存放在寄存器中,寄存器的名称在指令中的操作数中给出。在寄存器中存取操作数,可以获得较快的访问速度</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220908102644.png" alt="image-20220908102644381" style="zoom:67%;" / loading="lazy"></p><h3 id="存储器操作数寻址">存储器操作数寻址</h3><p>存储器操作数寻址也称为 <strong>内存操作数寻址</strong>,操作数存放在存储器中。物理地址是存储器单元在物理空间中的编号,逻辑地址是存储器单元在分段式管理中的逻辑编号。由于程序被装载入存储器中的位置由操作系统在程序载入的时候决定在编写程序时无法确定指令中使用的存储单元在程序载入存储器后的物理地址,<strong>只能使用逻辑地址来描述指令中用到的存储单元</strong>。CPU 在分析指令时使用内部的 <strong>段页式管理部件</strong> 将指令中的逻辑地址转换为对应的物理地址,再通过总线系统访问实际的物理存储单元</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220908103013.png" alt="image-20220908103013429" style="zoom:67%;" / loading="lazy"></p><ul><li><p>段寄存器名称也称为 <strong>段超越前缀</strong>,表示存放操作数的存储单元所在的逻辑段</p><ul><li><p><strong>代码段</strong> 存放当前正在运行的程序的机器指令,段寄存器为 <code>CS</code></p></li><li><p><strong>数据段</strong> 存放当前程序中使用的数据,段寄存器为 <code>DS</code></p></li><li><p><strong>堆栈段</strong> 存放需要具有先进后出特性的数据,段寄存器为 <code>SS</code></p></li><li><p><strong>附加段</strong> 也用于存放当前程序中使用的数据,段寄存器为 <code>ES</code></p></li></ul></li><li><p>在实模式下,CPU 将逻辑地址中的段基址乘以 16 后加上偏移地址就得到了操作数的物理地址,从对应的物理存储单元中存取操作数</p></li></ul><p>根据不同的应用场景,逻辑地址中的偏移地址表达式有 5 种不同的格式,分别对应 5 种存储器操作数寻址方法:直接寻址、寄存器间接寻址、基址寻址、变址寻址和基址加变址寻址。</p><h4 id="直接寻址">直接寻址</h4><ol type="1"><li><p>段寄存器:[偏移地址]</p><p>偏移地址表达式直接给出存储单元的偏移地址值。这种格式允许操作数存放在不同的逻辑段,<strong>段寄存器名称不可以省略</strong>。但手工计算比较烦琐容易出错,一般情况下不建议使用这种书写格式</p><p>比如:<code>MOV AL, ES:[2CH]</code></p></li><li><p>段寄存器:变量名</p><p>汇编语言中可以使用 <strong>伪指令</strong> 为存储单元命名,即存储单元的变量名,也称为 <strong>符号地址</strong>。汇编器将自动计算出该存储单元的偏移地址。由于变量名中本身蕴涵了其所在逻辑段的名称,因此在书写逻辑地址时,<strong>段寄存器名称可以省略不写</strong></p><p>比如:<code>MOV AX, ES:YY</code> 可以简写为 <code>MOV AX, YY</code></p></li></ol><h4 id="寄存器间接寻址">寄存器间接寻址</h4><p> 寄存器间接寻址又称间接寻址,间址。操作数在内存单元,该单元的段基址在 <strong>段寄存器</strong> 中,偏移地址在 <strong>间址寄存器</strong> 中,CPU 首先进行地址计算。</p><ol type="1"><li><p>段寄存器:[间址寄存器]</p><p>偏移地址表达式给出的间址寄存器用于存放操作数的 <strong>偏移地址</strong>。注意:只有一些特别指定的通用寄存器能够作为间址寄存器使用</p><table><thead><tr class="header"><th>间址寄存器</th><th>约定访问的逻辑段</th><th>寻址位数</th></tr></thead><tbody><tr class="odd"><td>BP</td><td>堆栈段</td><td>16 位</td></tr><tr class="even"><td>BX,SI,DI</td><td>数据段</td><td>16 位</td></tr><tr class="odd"><td>EBP,ESP</td><td>堆栈段</td><td>32 位</td></tr><tr class="even"><td>EAX~EDX,ESI,EDI</td><td>数据段</td><td>32 位</td></tr></tbody></table></li><li><p>[间址寄存器]</p><p>当间址访问的操作数位于的逻辑段就是间址寄存器 <strong>约定访问的逻辑段</strong> 时,逻辑地址中的段寄存器的名称可以省略不写;反之,不可以省略</p><blockquote><p>比如,设数据段 <code>BUF</code> 字节单元的内容为 <span class="math inline">\(55H\)</span>,取出该数 <span class="math inline">\(\to\)</span> <code>AL</code></p><pre class="language-asm" data-language="asm"><code class="language-asm">>mov DS, 数据段段基址>mov BX, BUF单元的偏移地址>mov AL, DS:[BX]>;由于BX间址,约定访问的是数据段,所以DS:可以省略,如下>mov AL, [BX]</code></pre></blockquote></li></ol><h4 id="基址寻址">基址寻址</h4><p>该寻址方式的偏移地址由两部分组成。一部分在 <strong>基址寄存器</strong> 中,另一部分为常量</p><ol type="1"><li><p>段寄存器:[基址寄存器+位移量]</p><p>偏移地址表达式由基址寄存器和位移量的 <strong>和</strong> 构成。注意:只有一些特别指定的通用寄存器能够作为基址寄存器使用</p><table><thead><tr class="header"><th>基址寄存器</th><th>约定访问的逻辑段</th><th>寻址位数</th></tr></thead><tbody><tr class="odd"><td>BP</td><td>堆栈段</td><td>16 位</td></tr><tr class="even"><td>BX</td><td>数据段</td><td>16 位</td></tr><tr class="odd"><td>EBP,ESP</td><td>堆栈段</td><td>32 位</td></tr><tr class="even"><td>EAX~EDX,ESI,EDI</td><td>数据段</td><td>32 位</td></tr></tbody></table></li><li><p>[基址寄存器+位移量]</p><p>与寄存器间址寻址一样,访问约定的逻辑段时,可以省略段寄存器</p><blockquote><p>比如,设数据段 <strong>BUF</strong> 单元按照地址从低到高依次存放 <span class="math inline">\(78H,56H,34H,12H\)</span>,观察下列代码给出 <code>DH</code> 和 <code>DX</code> 的值</p><pre class="language-asm" data-language="asm"><code class="language-asm">>mov DS, 数据段段基址>mov BX, BUF单元偏移地址>mov DH, [BX+1]; DH = 56H>mov DX, [BX+2]; DX = 1234H</code></pre></blockquote></li></ol><h4 id="变址寻址">变址寻址</h4><p>变址寻址,根据有无 <strong>比例因子</strong> 可以分为两种形式</p><ol type="1"><li><p>段寄存器:[比例因子*变址寄存器+位移量]</p><p>偏移地址表达式由“比例因子*变址寄存器+位移量”构成。注意:只有一些特别指定的通用寄存器能够作为变址寄存器使用,且比例因子只能是<span class="math inline">\(1,2,4,8\)</span>中的一个数</p><table><thead><tr class="header"><th>变址寄存器</th><th>约定访问的逻辑段</th><th>适用于</th></tr></thead><tbody><tr class="odd"><td>SI,DI</td><td>数据段</td><td>无比例因子,16 位</td></tr><tr class="even"><td>EBP</td><td>堆栈段</td><td>有比例因子,32 位</td></tr><tr class="odd"><td>EAX~EDX,ESI,EDI</td><td>数据段</td><td>有比例因子,32 位</td></tr></tbody></table></li><li><p>[比例因子*变址寄存器+位移量]</p><p>同样的,访问约定的逻辑段时,段寄存器可以省略</p><p>比如,<code>mov AH, DS:[4*EBX+3]</code>,也可以写作 <code>mov AH, [4*EBX+3]</code></p></li><li><p>段寄存器:[变址寄存器+位移量]</p><p>注意,无比例因子时,只有 <code>SI</code> 和 <code>DI</code> 能作为变址寄存器使用</p></li><li><p>[变址寄存器+位移量]</p><p>同样的,访问约定的逻辑段时,段寄存器可以省略</p><p>比如,<code>mov AH, SS:[SI+3]</code>,也可以写作 <code>mov AH, [SI+3]</code></p></li></ol><h4 id="基址加变址寻址">基址加变址寻址</h4><p>基址加变址寻址方式也称为 <strong>基加变寻址方式</strong>,是基址寻址和变址寻址两种寻址方式的结合,根据是否带有比例因子,也有两种书写格式。实际编程中,基址加变址寻址方式由于有基地址和变址地址两个参数,特别适合表示 <strong>二维下标</strong>,对二维数组进行访问。使用了位移量的基址加变址寻址方式,常用于对 <strong>结构体数据</strong> 进行访问,此时用基地址定位结构体,用位移量定位结构体中的数据项,用变址地址定位数据项中的每个元素</p><ol type="1"><li>段寄存器:[基址寄存器+比例因子*变址寄存器+位移量]</li><li>[基址寄存器+比例因子*变址寄存器+位移量]</li><li>段寄存器:[基址寄存器+变址寄存器+位移量]</li><li>[基址寄存器+变址寄存器+位移量]</li></ol><h2 id="汇编语言语法规则">汇编语言语法规则</h2><h3 id="语句类型和格式">语句类型和格式</h3><p>汇编语言源程序包括的语句类型为:<strong>指令性语句</strong> 和 <strong>指示性语句</strong>,指令性语句即 <strong>符号指令</strong>,指示性语句包括 <strong>伪指令</strong> 和 <strong>宏指令</strong>(比较特殊,第四章介绍)</p><p>符号指令:即经汇编后,转换为对应的机器指令供 CPU 读取并解析执行,其格式为:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220913102114.png" alt="image-20220913102107685" style="zoom: 67%;" / loading="lazy"></p><p>伪指令:是非机器指令,是在汇编链接期间进行操作的。为汇编程序,链接程序提供汇编链接信息,其格式为:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220913102142.png" alt="image-20220913102142468" style="zoom:67%;" / loading="lazy"></p><blockquote><p>标号名、变量名命名规则:以除数字以外的字母或符号开头, 后跟字母、数字…长度 <span class="math inline">\(\le 31\)</span> 个字符</p></blockquote><h3 id="常用伪指令">常用伪指令</h3><ol type="1"><li><p>数据定义伪指令</p><ol type="1"><li><p>字节定义伪指令 <code>DB</code>(Define Byte) 格式:变量名+<code>DB</code>+一个或多个用逗号间隔的单字节数</p><blockquote><p>可以使用长度符合的立即数</p><p>可以使用字符串,等效于多个字符型的立即数</p><p>可以使用随机数或者使用重复器 <code>DUP</code></p><p><code>DUP</code> 前面是重复的次数,括号内为生成的数据(可以为?)</p><pre class="language-asm" data-language="asm"><code class="language-asm">>N1 DB ?,?,?; 生成三个随机单字节数>N2 DB 3DUP('A');生成三个以逗号分隔的'A'</code></pre></blockquote></li><li><p>字定义伪指令 <code>DW</code>(Define Word) 格式:变量名+<code>DW</code>+一个或多个用逗号间隔的双字节数</p><blockquote><p>注意多字节存入内存时的顺序:先低位再高位</p><p>注意传入的立即数如果长度过短,会自动补前缀 0</p><p>注意如果传入的立即数使用 16 进制表示且前缀为字母,需要手动添加前缀 0,防止与变量混淆</p></blockquote></li><li><p>双字定义伪指令 <code>DD</code>(Define Double)</p><p>格式:变量名+<code>DD</code>+一个或多个用逗号间隔的四字节数</p><blockquote><p>变量名 DF 一串用逗号间隔的 6 字节数 变量名 DQ 一串用逗号间隔的 8 字节数 变量名 DT 一串用逗号间隔的 10 字节数</p></blockquote></li></ol></li><li><p>符号定义伪指令</p><p>源程序中使用到表达式时,直接用符号名即可,<strong>汇编</strong> 后符号名将替换为表达式的常数值</p><ol type="1"><li><p>等值伪指令 <code>EQU</code></p><p>格式:符号常数 + <code>EQU</code> + 表达式</p></li><li><p>等号伪指令 <code>=</code></p><p>格式:符号常数 + <code>=</code> + 表达式</p></li></ol><blockquote><p>两个指令的区别:</p><p>用 <code>EQU</code> 定义的符号常数,其值在后继语句中 <strong>不能更改</strong></p><p>用 <code>=</code> 定义的符号常数,其值在后继语句中 <strong>可以重新定义</strong></p></blockquote></li></ol><h3 id="常用运算符">常用运算符</h3><ol type="1"><li><p><code>$</code> 运算符</p><p>功能:<code>$</code> 运算符可以返回汇编地址计数器的当前值,通常使用 <code>$</code> 运算符计算变量在逻辑段中占用的字节总数</p><pre class="language-asm" data-language="asm"><code class="language-asm">BUF DB 'THE QUICK BROWN FOX';字符串长度19LLL EQU $-BUF;汇编后符号常数LLL的值即为19</code></pre></li><li><p><code>SEG</code> 运算符</p><p>格式:<code>SEG</code>+段名/变量名/标号名</p><p>功能:计算某一逻辑段的 <strong>段基址</strong></p><pre class="language-asm" data-language="asm"><code class="language-asm">MOV AX,SEG BUF;变量BUF是堆栈段中的存储单元,假设堆栈段的段基址为2000H,则(AX) = 2000H</code></pre><blockquote><p>注意:段名本身含有段基址的信息,所以如果是提取段名的段基址可以省略 <code>SEG</code></p></blockquote></li><li><p><code>OFFSET</code> 运算符</p><p>格式:<code>OFFSET</code>+变量名/标号名</p><p>功能:算出逻辑段中某个变量或标号所在单元相对于段首的 <strong>偏移地址</strong></p><pre class="language-asm" data-language="asm"><code class="language-asm">MOV AX,SEG DATA ;获取数据段段基址,本处SEG可省略MOV DS,AX ;指定数据段段基址MOV BX,OFFSET BUF;获取变量BUF的偏移地址,假设为12HMOV AL,[BX] ;用[]提取偏移地址内容,此处省略了DS:</code></pre></li><li><p><code>PTR</code> 运算符</p><blockquote><p>汇编语言规定在读写存储器操作数时,<strong>指令中的源操作数和日标操作数的类型属性必须一致</strong>,在出现不一致的情况下,可以使用 <code>PTR</code> 运算符临时修改其中的存储器操作数,即变量的属性,<strong>使源目两个操作数类型属性一致</strong>。另外,PTR 运算符也可用于修改标号的类型属性。</p><blockquote><p>操作数类型:BYTE(字节)、WORD(字)、DWORD(双字)</p><p>标号类型:FAR(远)、NEAR(近)</p></blockquote><p>特别注意:这样的强制修改是临时性的,仅在使用 <code>PTR</code> 运算符的指令中发挥作用,指令执行完毕后变量的类型属性仍然保持原有的属性不变。</p></blockquote><p>格式:类型说明符+<code>PTR</code>+地址表达式</p><p>功能:</p><ol type="1"><li><p>指令的操作数至少有一个类型属性要确定,否则必须用 PTR 运算符说明其中的内存操作数的类型</p></li><li><p>若两个操作数的类型属性都确定,则必须保持一致。否则必须用 PTR 运算符改变其中的内存操作数的类型,以保持前后属性一致</p><blockquote><p>类型属性确定的操作数:寄存器(长度固定),变量名直接寻址的操作数(变量定义的时候长度固定)</p><p>类型属性不确定的操作数:立即数,非变量名直接寻址的操作数(间址、基址、变址、变基、偏移地址的直接寻址)</p></blockquote></li></ol><pre class="language-asm" data-language="asm"><code class="language-asm">BUF DB 11H,22H,33H,44HWBUF DW ?,?XX DB 0FFH,0MOV AX,BUF ;原目操作数类型都确定,源为字,目为字节,不一致XMOV AL,BUF ;原目操作数类型都确定,源为字节,目为字节,一致MOV AX,WORD PTR BUF ; AH=22H,AL=11HMOV BUF,1234H ;源操作数类型为字节,目标操作数为字,不一致MOV WORD PTR BUF,1234H ;BUF单元为34H,BUF+1单元为12H</code></pre></li></ol><h2 id="基本指令集">80486 基本指令集</h2><blockquote><p>为了说明方便,作出以下约定</p><ul><li><p>N 代表立即数,N8、N16、N32 代表 8、16、32 位立即数</p></li><li><p>R 代表寄存器操作数,R8、R16、R32 代表 8、16、32 位寄存器操作数</p></li><li><p>M 代表内存操作数,M8、M16、M32 代表 8、16、32 位内存操作数</p></li><li><p>S 代表段寄存器</p></li><li><p>OP 代表操作数</p></li><li><p>DO 代表目标操作数</p></li><li><p>SO 代表源操作数</p></li></ul></blockquote><p>注意:</p><ol type="1"><li>对于双操作数指令<ol type="1"><li>源、目操作数不可同为 <code>M</code> 或 <code>S</code></li><li>源、目操作数属性(长度)一致</li><li>当目标操作数为间址、变址、基址、基+变址的内存操作数,而源操作数为单字节/双字节立即数,则目标操作数必须用 <code>PTR</code> 说明类型</li></ol></li><li>对于单操作数指令 若操作数为间址、变址、基址、基+变址的内存操作数,则必须用 PTR 说明类型</li></ol><h3 id="传送类指令">传送类指令</h3><p> 传送类指令执行后,不影响状态标志,主要包括通用传送类指令和堆栈操作指令</p><h4 id="通用传送类指令">通用传送类指令</h4><ol type="1"><li><p><code>MOV</code> 数据传送指令</p><p>格式:<code>MOV DO, SO</code></p><p>功能:将 <code>SO</code> $$ <code>DO</code>,<code>SO</code> 不变</p><p><code>MOV</code> 指令指令支持的类型还是比较宽泛的,但注意:</p><ol type="1"><li>不允许向 <code>S</code> 写入 <code>N</code>(需要寄存器中转)</li><li>不允许向 <code>S</code> 写入 <code>S</code></li><li>不允许向 <code>M</code> 写入 <code>M</code></li><li><code>CS</code>(指令段寄存器)不允许作为 <code>DO</code></li></ol></li><li><p><code>MOVSX</code> 符号扩展指令</p><p>格式:<code>MOVSX R, SO</code></p><p>功能:将 <code>SO</code> $$ <code>DO</code>,将源操作数向高位进行扩展,<strong>用符号位进行填补</strong>,使其与目标操作数的字长相 同后再传送到目标操作数,源操作数不变</p><p>注意:<code>SO</code> 只能为 <code>R</code>,<code>DO</code> 只能为 <code>R/M</code></p></li><li><p><code>MOVZX</code> 符号扩展指令</p><p>格式:<code>MOVZX R, SO</code></p><p>功能:将 <code>SO</code> $$ <code>DO</code>,将源操作数向高位进行扩展,<strong>用 0 进行填补</strong>,使其与目标操作数的字长相 同后再传送到目标操作数,源操作数不变</p><p>注意:<code>SO</code> 只能为 <code>R</code>,<code>DO</code> 只能为 <code>R/M</code></p></li><li><p><code>LEA</code> 有效地址传送指令</p><p>格式:<code>LEA R16/R32, M</code></p><p>功能:计算内存单元的有效地址 <span class="math inline">\(\to\)</span> <code>DO</code></p><p>注意:有效地址其实就是其偏移地址,所以 <code>LEA BX, BUF</code> 等价于 <code>MOV BX, OFFSET BUF</code></p></li><li><p><code>XCHG</code> 交换传送指令</p><p>格式:<code>XCHG OP1, OP2</code></p><p>功能:完成两个操作数的互换</p><p>注意:<code>OP1</code> 和 <code>OP2</code> 只能同为 <code>R</code> 或者 <code>R+M</code></p></li></ol><h4 id="堆栈操作指令">堆栈操作指令</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220915104649.png" alt="image-20220915104642074" style="zoom: 67%;" / loading="lazy"></p><ul><li>堆栈段寄存器 <code>SS</code>:存放堆栈段段基址</li><li>堆栈指针 <code>SP</code>:存放栈顶单元的偏移地址</li><li>堆栈指针 <code>SP</code> 的初值决定了堆栈的大小,SP 始终指向堆栈的顶部,即始终指向最后压入堆栈的信息所在的单元</li><li>8086 的堆栈是向低地址方向延伸的,栈顶是 <strong>浮动</strong> 的,且一次进栈、出栈的数据至少是 2 字节。与正常的写入内存一直,保证高字节对(内存)高地址,低字节对(内存)低地址</li></ul><ol type="1"><li><p><code>PUSH</code> 进栈指令</p><p>格式:<code>PUSH SO</code></p><p>功能:将 <code>SO</code> 压入堆栈。操作过程是 <strong>先将堆栈指针向低地址方向进行调整,然后将操作数送入堆栈指针指向的栈顶单元中</strong>。当 <code>SO</code> 是字时,<code>SP</code> 的调整方法为 <span class="math inline">\((SP)-2\to (SP)\)</span>,放入堆栈中的字占用堆栈的 2 个字节单元</p><p>注意:</p><ol type="1"><li><code>SO</code> 可以是 <code>N16/32, R16/32, M16/32 S</code>(除了 <code>CS</code>)</li><li>当 <code>SO</code> 是 <code>M</code> 时,根据需要加上 <code>PTR</code> 运算符</li></ol></li><li><p><code>POP</code> 出栈指令</p><p>格式:<code>POP DO</code></p><p>功能:从堆栈中弹出一个字或双字,将其送入 <code>DO</code>。操作过程是 <strong>先将堆栈中取出的字或双字送入 <code>DO</code>,然后将堆栈指针向高地址方向进行调整</strong>。当 <code>DO</code> 是字时,从堆栈中取出一个字,即 2 个字节,<code>SP</code> 的调整方法为 <span class="math inline">\((SP)+2\to (SP)\)</span></p><p>注意:</p><ol type="1"><li><code>DO</code> 可以是 <code>R16/32, M16/32 S</code>(除了 <code>CS</code>)</li><li>当 <code>DO</code> 是 <code>M</code> 时,根据需要加上 <code>PTR</code> 运算符</li></ol></li></ol><h3 id="算术运算类指令">算术运算类指令</h3><table><thead><tr class="header"><th>分类</th><th>名称</th><th>格式</th><th>功能</th><th>O S Z A P C</th></tr></thead><tbody><tr class="odd"><td>加法指令</td><td>加法指令</td><td>ADD DO, SO</td><td>加法</td><td>O S Z A P C</td></tr><tr class="even"><td></td><td>带进位加法指令</td><td>ADC DO, SO</td><td>带进位加法</td><td>O S Z A P C</td></tr><tr class="odd"><td></td><td>加 1 指令</td><td>INC OP</td><td>加 1</td><td>O S Z A P</td></tr><tr class="even"><td>减法指令</td><td>减法指令</td><td>SUB DO, SO</td><td>减法</td><td>O S Z A P C</td></tr><tr class="odd"><td></td><td>带借位减法指令</td><td>SBB DO, SO</td><td>带借位减法</td><td>O S Z A P C</td></tr><tr class="even"><td></td><td>减 1 指令</td><td>DEC OP</td><td>减 1</td><td>O S Z A P</td></tr><tr class="odd"><td></td><td>比较指令</td><td>CMP DO, SO</td><td>比较</td><td>O S Z A P C</td></tr><tr class="even"><td></td><td>求补指令</td><td>NEG OP</td><td>求补码</td><td>O S Z A P C</td></tr><tr class="odd"><td>乘法指令</td><td>无符号数乘法</td><td>MUL SO</td><td>不带符号数乘法</td><td>O C</td></tr><tr class="even"><td></td><td>带符号数乘法</td><td>IMUL SO</td><td>带符号数乘法</td><td>O C</td></tr><tr class="odd"><td>除法指令</td><td>无符号数除法</td><td>DIV SO</td><td>不带符号数除法</td><td>不影响</td></tr><tr class="even"><td></td><td>带符号数乘法</td><td>IDIV SO</td><td>带符号数除法</td><td>不影响</td></tr><tr class="odd"><td></td><td>符号扩展指令</td><td>CBW(了解)</td><td>扩展 AL 中的符号</td><td>不影响</td></tr><tr class="even"><td></td><td>符号扩展指令</td><td>CWD(了解)</td><td>扩展 AX 中的符号</td><td>不影响</td></tr><tr class="odd"><td>十进制调整指令</td><td>DAA、AAD</td><td>十进制数调整</td><td></td><td>S Z A P C</td></tr></tbody></table><h4 id="加减运算类指令">加减运算类指令</h4><ol type="1"><li><p>二进制加减</p><p><code>ADD DO, SO</code> 二进制加法</p><p><code>SUB DO, SO</code> 二进制减法</p><p><code>ADC DO, SO</code> 二进制加进位</p><p><code>SBB DO, SO</code> 二进制减进位</p><p>注意:</p><ol type="1"><li>这四种操作影响全部标志</li><li>所谓加/减进位,指上条(影响 <code>C</code> 标)指令的 <code>C</code> 也参与运算,<code>ADC</code> 为加上 <code>C</code>,<code>SBB</code> 为减去 <code>C</code></li><li><code>SO</code> 可以为 <code>N/R/M</code>,<code>DO</code> 可以为 <code>R/M</code>,但同样的,双 <code>S</code> 或者 <code>M</code> 都是不允许的</li><li>根据需要 <code>PTR</code></li></ol></li><li><p>二进制自增,自减</p><p><code>INC DO</code> 二进制自增</p><p><code>DEC DO</code> 二进制自减</p><p>注意:</p><ol type="1"><li>这两种操作影响除了 <code>C</code> 之外的标志</li><li><code>DO</code> 可以为 <code>R/M</code></li><li>根据需要 <code>PTR</code>,对多字节自增时,注意拼接后再自增,而非每个字节都自增</li></ol></li><li><p><code>NEG</code> 二进制求补指令</p><p>格式:<code>NEG DO</code></p><p>功能:将 <code>DO</code> 变为其负值</p><p>注意:<code>DO</code> 为 <code>R/M</code></p></li><li><p><code>CMP</code> 比较指令</p><p>格式:<code>CMP DO, SO</code></p><p>功能:将目标操作数减去源操作数,但保持目标操作数不变,依据减法运算的情况 6 个状态标志位</p><p>注意:</p><ol type="1"><li><code>DO</code> 为 <code>R/M</code>,<code>SO</code> 为等长 <code>R/M</code> 或者不超过 <code>DO</code> 长度的 <code>N</code></li><li>该指令一般用于后续条件转移指令</li></ol></li></ol><h4 id="乘除运算类指令">乘除运算类指令</h4><ol type="1"><li><p><code>MUL</code> 无符号二进制数乘法</p><p>格式:<code>MUL 乘数</code></p><p>功能:将同为 <strong>无符号数</strong> 并且 <strong>字长相等</strong> 的被乘数与乘数相乘,乘积送入指定寄存器</p><p>注意:</p><ol type="1"><li>乘数显式给定(<code>R/M</code>),被乘数和乘积均为隐含操作数</li><li>字节乘法,被乘数默认放 <code>AL</code>,中,得到的 16 位乘积送入 <code>AX</code> 中</li><li>字乘法,被乘数默认放 <code>AX</code> 中,得到的 32 位乘积的低 16 位送入 <code>AX</code> 中,高 16 位送入 <code>DX</code> 中</li><li>对标志位的影响:如果乘积的高半部分为 0,则 <code>C</code> 标和 <code>O</code> 标都置 0,否则置 1</li></ol></li><li><p><code>IMUL</code> 有符号二进制数乘法</p><p>格式:<code>IMUL</code> 有三种格式,分别为:<code>IMUL 乘数</code>(同 <code>MUL</code>),<code>IMUL DO, SO</code>,<code>IMUL DO, OP1, OP2</code></p><p>功能:第一种格式同 <code>MUL</code>,第二种格式为 <span class="math inline">\(DO \times SO \to DO\)</span>,第三种格式为 <span class="math inline">\(OP_1 \times OP2 \to DO\)</span></p></li><li><p><code>DIV</code> 无符号二进制数除法</p><p>格式:<code>DIV 除数</code></p><p>功能:将同为无符号数并且 <strong>字长为除数的双倍长度的被除数与除数相除</strong>,运算得到的商和余数送入指定寄存器</p><p>注意:</p><ol type="1"><li>除数显式给定(<code>R/M</code>),被乘数,商和余数均为隐含操作数</li><li>字节除法:除数为 8 位 <code>R/M</code>,16 位被除数默认放在 <code>AX</code> 中,得到的 8 位商送入 <code>AL</code> 中,8 位余数送入 <code>AH</code> 中</li><li>字除法:除数为 16 位 <code>R/M</code>,32 位被除数的高 16 位默认放在 <code>DX</code> 中,低 16 位默认放在 <code>AX</code> 中,得到的 16 位商送入 <code>AX</code> 中,16 位余数送入 <code>DX</code> 中</li><li>对标志位:无定义</li><li>当除数为 0,或者运算后的商超过定义字长,会溢出产生 <strong>0 型中断</strong></li></ol></li><li><p><code>IDIV</code> 有符号二进制数除法</p><p>格式:<code>IDIV 除数</code></p><p>注意:</p><ol type="1"><li>对标志位:无定义</li><li>当除数为 0,或者运算后的商超过定义字长,会溢出产生 <strong>0 型中断</strong></li></ol></li></ol><h3 id="bcd-码调整指令">BCD 码调整指令</h3><ul><li><p>组合 BCD 码(紧凑型):一个字节含有 2 位 BCD 码</p></li><li><p>未组合 BCD 码(非紧凑型):一个字节含有 1 位 BCD 码</p></li></ul><blockquote><p>BCD 码数是用 4 位二进数代表 1 位十进数,运算法则应是:“逢十进一,借一当十”</p></blockquote><p>使用二进制加法运算指令对用 BCD 码表示的十进制数进行运算,需要进行相应的修正,修正依据二进制加法运算完成后 C 标志和 A 标志的值以及运算结果中的高 4 位(低 4 位)是否大于 9,相应地将运算结果加上 <code>06H</code>、<code>60H</code> 或 <code>66H</code> 进行修正得到正确的 BCD 码结果</p><p>修正:在加法运算中:加 6(0110)</p><ul><li><code>A</code> 标志为 1(低四位向高位进位):加上 <code>06H</code></li><li><code>C</code> 标志为 1(高四位向更高位进位):加上 <code>60H</code></li><li>高四位或者第四位的值<span class="math inline">\(\ge 9\)</span>,对应四位加 <code>0110</code></li><li>修正可能发生两次</li></ul><h5 id="组合-bcd-码十进制数的算术运算调整指令">组合 BCD 码十进制数的算术运算调整指令</h5><ol type="1"><li><p><code>DAA</code> 压缩的 BCD 加法十进制调整指令</p><p>格式:<code>DAA</code></p><p>功能:对存放在 <code>AL</code> 中的由两个组合 BCD 码数相加的和进行修正,得到正确的组合 BCD 码结果</p><ol type="1"><li>如果 <code>AL</code> 低 4 位大于 9 或者 <code>A</code> 标志 = 1,则 <span class="math inline">\((AL)+06H\to(AL)\)</span>,并将 <code>A</code> 标志置 1</li><li>如果 <code>AL</code> 高 4 位大于 9 或者 <code>C</code> 标志 = 1,则 <span class="math inline">\((AL)+60H\to(AL)\)</span>,并将 <code>C</code> 标志置 1</li><li>如果以上条件均不满足,则不需要对 AL 寄存器中的和进行修正</li></ol><p>注意:</p><ol type="1"><li>对标志位的影响:DAA 指令执行后,影响除了 <code>O</code> 标以外的五个标志位</li></ol></li></ol><h3 id="传送和调用类指令">传送和调用类指令</h3><table><thead><tr class="header"><th>分类</th><th>指令</th><th>功能</th></tr></thead><tbody><tr class="odd"><td>无条件转移指令</td><td>JMP OP</td><td>无条件转移</td></tr><tr class="even"><td>条件转移指令</td><td>JNZ OP(等)</td><td>根据上一条指令设置的标志位的情况转移</td></tr><tr class="odd"><td>循环指令</td><td>LOOP OP</td><td>计数非零循环</td></tr><tr class="even"><td></td><td>LOOPE/LOOPZ OP</td><td>计数非零循环且结果为 0 循环</td></tr><tr class="odd"><td></td><td>LOOPNE/LOOPNZ OP</td><td>计数非零循环且结果不为 0 循环</td></tr><tr class="even"><td>子程序调用指令</td><td>CALL OP</td><td>调用子程序</td></tr><tr class="odd"><td>子程序返回指令</td><td>RET</td><td>从子程序返回</td></tr><tr class="even"><td>中断指令</td><td>INTN</td><td>软中断</td></tr><tr class="odd"><td></td><td>INTO</td><td>溢出时中断</td></tr><tr class="even"><td></td><td>IRET</td><td>中断返回</td></tr></tbody></table><ul><li><p>按照转移条件分:无条件转移和有条件转移</p></li><li><p>按照转移范围分:段内转移和段间转移</p></li><li><p>按照获取转移地址的方法分:直接转移和间接转移</p></li></ul><h4 id="无条件转移指令">无条件转移指令</h4><p><code>JMP</code></p><p>格式:<code>JMP 标号</code></p><p>功能:无条件转移指令在执行时,无须任何前提条件,将控制转移到 <strong>目标指令</strong> 处</p><p>注意:</p><ol type="1"><li>格局转移范围和获取转移地址的方法来分,<code>JMP</code> 一共有四种类型操作。只要掌握其中的 <strong>段内直接转移</strong> 即可</li><li>在标号的前面加上 <code>SHORT</code> 运算符,则转移变成短转移,此时转移的范围为相对 JMP 指令地址 <span class="math inline">\(-126\sim +129B\)</span>,如果目标指令的地址超过这一范围,汇编程序将给出错误</li></ol><h4 id="条件转移指令">条件转移指令</h4><p>功能:根据上一条指令影响的状态位判断是否转移</p><p>注意:</p><ol type="1"><li><p>条件转移指令全部为 <strong>段内转移</strong></p></li><li><p>目标地址在当前指令的 <span class="math inline">\(-126\sim +129B\)</span> 范围内。</p></li><li><p>目标地址由操作数 <code>OP</code> 确定</p></li></ol><p>单个条件标志的设置情况转移:</p><table><thead><tr class="header"><th>指令</th><th>功能</th><th>转移条件</th></tr></thead><tbody><tr class="odd"><td>JZ / JE OP</td><td>结果为 0 / 相等则转移</td><td>Z = 1</td></tr><tr class="even"><td>JNZ / JNE OP</td><td>结果不为 0 / 不相等则转移</td><td>Z = 0</td></tr><tr class="odd"><td>JS OP</td><td>结果为负则转移</td><td>S = 1</td></tr><tr class="even"><td>JNS OP</td><td>结果不为负则转移</td><td>S = 0</td></tr><tr class="odd"><td>JO OP</td><td>溢出则转移</td><td>O = 1</td></tr><tr class="even"><td>JNO OP</td><td>不溢出则转移</td><td>O = 0</td></tr><tr class="odd"><td>JP / JPE OP</td><td>奇偶位为 1 则转移</td><td>P = 1</td></tr><tr class="even"><td>JNP / JPO OP</td><td>奇偶位为 0 则转移</td><td>P = 0</td></tr><tr class="odd"><td>JCXZ OP</td><td>CX = 0 则转移</td><td>CX = 0</td></tr></tbody></table><p>根据组合条件标志设置的情况转移(主要用来比较两个数大小)</p><p>无符号数大小比较:</p><table><thead><tr class="header"><th>指令</th><th>功能</th><th>转移条件</th></tr></thead><tbody><tr class="odd"><td>JC OP</td><td>低于/不高于等于/有借位则转移</td><td>C = 1</td></tr><tr class="even"><td>JNC OP</td><td>不低于/高于等于/无借位则转移</td><td>C = 0</td></tr><tr class="odd"><td>JNA OP</td><td>低于或等于/不高于则转移</td><td>C <span class="math inline">\(\lor\)</span> Z = 1</td></tr><tr class="even"><td>JA OP</td><td>不低于或等于/高于则转移</td><td>C <span class="math inline">\(\lor\)</span> Z = 0</td></tr></tbody></table><p>有符号数大小比较:</p><table><thead><tr class="header"><th>指令</th><th>功能</th><th>转移条件</th></tr></thead><tbody><tr class="odd"><td>JL OP</td><td>小于/不大于等于则转移</td><td>S <span class="math inline">\(\oplus\)</span> O = 1 且 Z = 0</td></tr><tr class="even"><td>JGE OP</td><td>不小于/大于或等于则转移</td><td>S <span class="math inline">\(\oplus\)</span> O = 0 或 Z = 1</td></tr><tr class="odd"><td>JLE OP</td><td>小于或等于/不大于则转移</td><td>S <span class="math inline">\(\oplus\)</span> O = 1 或 Z = 1</td></tr><tr class="even"><td>JG OP</td><td>不小于等于/大于则转移</td><td>S <span class="math inline">\(\oplus\)</span> O = 0 且 Z = 0</td></tr></tbody></table><h4 id="循环指令">循环指令</h4><p><code>LOOP</code> 循环指令</p><p>格式: <code>LOOP 标号</code></p><p>功能:判断 <code>CX</code> 中的值,不为 0 则转至标号处执行程序,如果为 0 则向下执行</p><p>注意:</p><ol type="1"><li>每次执行都会自动执行 <span class="math inline">\(CX - 1 \to CX\)</span></li></ol><p>栗子:数出长度为 10 的,以 STRING 为首地址的字符串中的空格个数</p><pre class="language-asm" data-language="asm"><code class="language-asm">LEA SI, STRING ; 获得其偏移地址MOV CX, 0AH ; 指定循环次数为10次MOV AL, 20H ; 空格的ASCII码为20HMOV AH, 0H ; 结果在AH中AGAIN: CMP AL, [SI]; 比较大小JZ ADDA; JZ判断是否相等(z=0),是的话说明为空格,跳到ADDA执行答案自增JMP CONT; 不相等继续运行,跳转到CONT,SI自增ADDA: INC AHCONT: INC SILOOP AGAIN; 继续循环</code></pre><h5 id="子程序调用及返回指令">子程序调用及返回指令</h5><p>在汇编语言中子程序也称为过程,使用过程定义语句进行定义,<strong>过程的名称即为子程序的名称</strong>,在子程序定义中,用属性来标明子程序与主程序是否处于同一个代码段,<strong>如果子程序和主程序位于同一个代码段,则子程序的属性定义为 NEAR</strong> 属性,对该子程序的调用称为 <strong>段内调用</strong>;<strong>如果子程序和主程序分别位于不同的代码段,则子程序的属性定义为 FAR</strong> 属性,对子程序的调用称为 <strong>段间调用</strong></p><p>汇编语言的过程定义语句</p><pre class="language-asm" data-language="asm"><code class="language-asm">PNamePROC NEAR/FAR...RETPNameENDP</code></pre><ul><li>PName:子程序名,以字母开头,长度≤ 31。经汇编之后, 过程名就是子程序第一条指令的地址</li><li>PROC/ENDP 是子程序的定界语句</li><li>属性<ul><li>NEAR(缺省值)代表近过程,即该子程序和调用它的那条指令在同一个代码段</li><li>FAR 代表远过程,即该子程序和调用它的那条指令不在同一个代码段</li><li>RET 子程序返回指令</li></ul></li></ul><ol type="1"><li><p><code>CALL</code> 子程序调用指令</p><p>格式:段内直接调用:<code>CALL 程序名</code>,段内间接调用:<code>CALL R/M</code></p><p>功能: 调用子程序,即无条件转到子程序的第一条指令</p><p>注意:</p><ol type="1"><li><p><code>CALL</code> 命令其实就是 <strong>保存现场</strong> 并 <strong>JMP 跳转</strong></p></li><li><p>段内调用 <code>CALL</code>,首先,调整堆栈指针,$(SP) -2 (SP) <span class="math inline">\(;然后,将 `CALL` 指令的下一条指令的地址,即 **断点的偏移地址** 压入堆栈中保存,\)</span>(IP) (SP)$;最后,将子程序的入口的偏移地址 <span class="math inline">\(\to(IP)\)</span>,同时 <span class="math inline">\((CS)\)</span> 保持不变,程序控制由主程序转到子程序</p></li><li><p>段间调用 <code>CALL</code>,首先,调整堆栈指针 $(SP) -4 (SP) $;然后,将 <code>CALL</code> 指令的下一条指令的地址,即 <strong>断点的段基址和偏移地址</strong> 依次压入堆栈中保存 <span class="math inline">\((IP) \to (16*(SS)+SP)\)</span>;最后,将子程序的入口的段基址 <span class="math inline">\(\to(CS)\)</span>,偏移地址 <span class="math inline">\(\to(IP)\)</span>,程序控制由主程序转到子程序</p></li></ol></li><li><p><code>RET</code> 子程序返回指令</p><p>格式:<code>RET</code></p><p>功能:从子程序中返回主程序需要执行返回指令。<strong>返回指令是子程序中最后一条指令</strong>,对应于段内调用和段间调用,返回指令分为段内返回和段间返回,此外,返回指令有无参数和有参数两种形式</p><p>注意:</p><ol type="1"><li><code>RET</code> 命令其实就是 <strong>恢复现场</strong></li><li>段内调用 <code>RET</code>,从堆栈的栈顶弹出之前保存的断点指令地址,<span class="math inline">\((SP) \to (IP)\)</span>,恢复堆栈 <span class="math inline">\((SP)+2\to(SP)\)</span>,同时 <code>CS</code> 内容保持不变。</li><li>段间调用 <code>RET</code>,从堆栈的栈顶弹出之前保存的断点指令的偏移地址,<span class="math inline">\((SP) \to (IP)\)</span>,然后弹出断点指令的段地址,<span class="math inline">\((SP) \to (CS)\)</span>,恢复堆栈 <span class="math inline">\((SP +4) \to (SP)\)</span></li></ol></li></ol><h3 id="逻辑运算和移位指令">逻辑运算和移位指令</h3><table><thead><tr class="header"><th>分类</th><th>名称</th><th>格式</th><th>功能</th><th>O S Z A P C</th></tr></thead><tbody><tr class="odd"><td>逻辑运算指令</td><td>逻辑与指令</td><td>AND DO, SO</td><td>与</td><td>O S Z P C</td></tr><tr class="even"><td></td><td>逻辑或指令</td><td>OR DO, SO</td><td>或</td><td>O S Z P C</td></tr><tr class="odd"><td></td><td>逻辑非指令</td><td>NOT OP</td><td>非</td><td>不影响</td></tr><tr class="even"><td></td><td>逻辑异或指令</td><td>XOR DO, SO</td><td>异或</td><td>O S Z P C</td></tr><tr class="odd"><td></td><td>测试指令</td><td>TEST DO, SO</td><td>测试</td><td>O S Z P C</td></tr><tr class="even"><td>移位指令</td><td>逻辑左移指令</td><td>SHL OP, COUNT</td><td>逻辑左移</td><td>O S Z P C</td></tr><tr class="odd"><td></td><td>算术左移指令</td><td>SAL OP, COUNT</td><td>算术左移</td><td>O S Z P C</td></tr><tr class="even"><td></td><td>逻辑右移指令</td><td>SHR OP, COUNT</td><td>逻辑右移</td><td>O S Z P C</td></tr><tr class="odd"><td></td><td>算术右移移指令</td><td>SAR OP, COUNT</td><td>算术右移</td><td>O S Z P C</td></tr><tr class="even"><td>循环移位指令</td><td>循环左移指令</td><td>ROL OP, COUNT</td><td>循环左移</td><td>O C</td></tr><tr class="odd"><td></td><td>循环右移指令</td><td>ROR OP, COUNT</td><td>循环右移</td><td>O C</td></tr><tr class="even"><td></td><td>带进位循环左移</td><td>RCL OP, COUNT</td><td>带进位循环左移</td><td>O C</td></tr><tr class="odd"><td></td><td>带进位循环右移</td><td>RCR OP, COUNT</td><td>带进位循环右移</td><td>O C</td></tr></tbody></table><h4 id="逻辑运算类指令">逻辑运算类指令</h4><ol type="1"><li><p><code>NOT</code> 逻辑非指令</p><p>格式:<code>NOT DO</code></p><p>功能:实现操作数的按位取反运算,并将结果赋予 <code>DO</code></p><p>注意:该操作不影响标志位</p></li><li><p><code>AND</code> 逻辑与指令</p><p>格式:<code>AND DO,SO</code></p><p>功能:实现两个操作数的按位与运算,并将结果赋予 <code>DO</code></p><p>注意:</p><ol type="1"><li>该操作影响操作位 <span class="math inline">\(C=0,O=0\)</span>,以及 <span class="math inline">\(P,S,Z\)</span></li><li>可以用于某些位置置 0,或者检测某些位置是否为 1</li></ol></li><li><p><code>OR</code> 逻辑或指令</p><p>格式:<code>OR DO,SO</code></p><p>功能:实现两个操作数的按位与运算,并将结果赋予 <code>DO</code></p><p>注意:</p><ol type="1"><li>该操作影响操作位 <span class="math inline">\(C=0,O=0\)</span>,以及 <span class="math inline">\(P,S,Z\)</span></li><li>可以用于某些位置置 1,或者检测某些位置是否为 0</li></ol></li><li><p><code>XOR</code> 异或指令</p><p>格式:<code>XOR DO,SO</code></p><p>功能:实现两个操作数的按位异或,并将结果赋予 <code>DO</code></p><p>注意:</p><ol type="1"><li>该操作影响操作位 <span class="math inline">\(C=0,O=0\)</span>,以及 <span class="math inline">\(P,S,Z\)</span></li><li>异或自己可以全置 0,或者用于将某些位数 <strong>取反</strong>(1 取反,0 不变)</li></ol></li><li><p><code>TEST</code> 测试指令</p><p>格式:<code>TEST DO,SO</code></p><p>功能:实现两个操作数的按位与运算,结果不保存,<strong>只影响标志位</strong></p></li></ol><h4 id="移位指令">移位指令</h4><ol type="1"><li><p><code>SAL/SAR</code> 算术移位指令</p><p>格式:<code>SAL/SAR OP, COUNT</code></p><p>功能:左移与逻辑移位相同,右移时用原本最高位补位(正数用 0,负数用 1)</p></li><li><p><code>SHL/SHR</code> 逻辑移位指令</p><p>格式:<code>SHL/SHR OP, COUNT</code></p><p>功能:正常移位</p></li><li><p><code>ROL/ROR</code> 循环移位指令</p><p>格式:<code>ROL/ROR OP, COUNT</code></p><p>功能:移位后,使用被移出的进行补位</p></li><li><p><code>RCL/RCR</code> 带进位的移位指令</p><p>格式:<code>RCL/RCR OP, COUNT</code></p><p>功能:移位后,使用之前的 <code>CF</code> 进行补位</p></li></ol><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220922103609.png" alt="image-20220922103609444" style="zoom:67%;" / loading="lazy"></p><h3 id="处理控制机类指令">处理控制机类指令</h3><table><thead><tr class="header"><th>名称</th><th>格式</th><th>功能(对标志位的影响)</th></tr></thead><tbody><tr class="odd"><td>进位标志清 0 指令</td><td>CLC</td><td>C = 0</td></tr><tr class="even"><td>进位标志置 1 指令</td><td>STC</td><td>C = 1</td></tr><tr class="odd"><td>进位标志取反</td><td>CMC</td><td>C = C</td></tr><tr class="even"><td>方向标志清 0 指令</td><td>CLD</td><td>D = 0</td></tr><tr class="odd"><td>方向标志置 1 指令</td><td>STD</td><td>D = 1</td></tr><tr class="even"><td>中断标志清 0 指令</td><td>CLI</td><td>I = 0</td></tr><tr class="odd"><td>中断标志置 1 指令</td><td>STI</td><td>I = 1</td></tr><tr class="even"><td>处理器等待指令</td><td>WAIT</td><td>处理器等待</td></tr><tr class="odd"><td>处理器交权指令</td><td>ESC</td><td>处理器交权</td></tr><tr class="even"><td>总线封锁前缀</td><td>LOCK</td><td>封锁总线</td></tr><tr class="odd"><td>处理器暂停指令</td><td>HLT</td><td>使处理器暂时处于停机状态</td></tr></tbody></table>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>太吾绘卷正式版出了!准备小溜一手!再次推荐王爽老师的《汇编语言》</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220922105447.png" alt="image-20220922105447471" style="zoom:67%;" /></p></summary>
<category term="汇编语言程序设计" scheme="http://lapras.xyz/categories/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="汇编" scheme="http://lapras.xyz/tags/%E6%B1%87%E7%BC%96/"/>
</entry>
<entry>
<title>Games101-Transform</title>
<link href="http://lapras.xyz/2022/09/20/918c829e.html"/>
<id>http://lapras.xyz/2022/09/20/918c829e.html</id>
<published>2022-09-20T11:00:00.000Z</published>
<updated>2022-10-07T11:35:25.837Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>本文是对 <a href="https://www.bilibili.com/video/BV1X7411F744?p=3">Game101</a> L3~L4 的笔记,阅读前请先保证对线性代数几何意义有着较为直观的理解,推荐配合食用 <a href="https://www.bilibili.com/video/BV1ys411472E">3Blue1Brown-线性代数的本质</a></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220920190945.jpeg" alt="img" style="zoom:80%;" / loading="lazy"></p><span id="more"></span><h2 id="向量变换">向量变换</h2><p>空间的变换可以用 <strong>基向量</strong> 的变换来加以描述,这些基向量可以构成单位矩阵。换言之,通过一个矩阵我们就可以描述空间的变化</p><h3 id="线性变换">线性变换</h3><p>利用指向 <span class="math inline">\(x\)</span> 轴正方向的的基向量 <span class="math inline">\(\hat x\)</span> 和指向 <span class="math inline">\(y\)</span> 轴正方向的基向量 <span class="math inline">\(\hat y\)</span>,可以很容易的得出所谓的变换矩阵。而将一个向量左乘一个变换矩阵得到一个新向量的过程,因为其按照矩阵乘法展开式为线性运算形式 <span class="math display">\[v' = M v \\ \\\left[\begin{array}{l}x^{\prime} \\y^{\prime}\end{array}\right]=\left[\begin{array}{ll}a & b \\c & d\end{array}\right]\left[\begin{array}{l}x \\y\end{array}\right]\\ \\x' = ax+by \\y' = cx+dy\]</span> 故称之为 <strong>线性变换</strong></p><p>约定,以下的 <span class="math inline">\(\hat x = \begin{pmatrix} 1 \\ 0 \end{pmatrix}\)</span>,<span class="math inline">\(\hat y = \begin{pmatrix} 0 \\ 1 \end{pmatrix}\)</span></p><h4 id="缩放-scale">缩放 Scale</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220919214655.png" alt="image-20220919214647983" style="zoom: 50%;" / loading="lazy"></p><p>可以使用如下的公式 <span class="math display">\[\left[\begin{array}{l}x^{\prime} \\y^{\prime}\end{array}\right]=\left[\begin{array}{cc}s_x & 0 \\0 & s_y\end{array}\right]\left[\begin{array}{l}x \\y\end{array}\right]\]</span> 也可以直接观察两个基向量的变化情况,<span class="math inline">\(\hat x' = \begin{pmatrix} s_x \\ 0 \end{pmatrix}\)</span>,<span class="math inline">\(\hat y' = \begin{pmatrix} 0 \\ s_y \end{pmatrix}\)</span></p><p>然后将两个新的基向量 <strong>拼合</strong> 即可组成变换矩阵,以上内容后文不再赘述</p><h4 id="对称-reflection">对称 Reflection</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220919220419.png" alt="image-20220919220419759" style="zoom:50%;" / loading="lazy"> <span class="math display">\[\left[\begin{array}{l}x^{\prime} \\y^{\prime}\end{array}\right]=\left[\begin{array}{cc}-1 & 0 \\0 & 1\end{array}\right]\left[\begin{array}{l}x \\y\end{array}\right]\]</span></p><h4 id="剪切-shear">剪切 Shear</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220919221109.png" alt="image-20220919221109356" style="zoom:50%;" / loading="lazy"> <span class="math display">\[\left[\begin{array}{l}x^{\prime} \\y^{\prime}\end{array}\right]=\left[\begin{array}{ll}1 & a \\0 & 1\end{array}\right]\left[\begin{array}{l}x \\y\end{array}\right]\]</span></p><h4 id="旋转-rotation">旋转 Rotation</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220919221229.png" alt="image-20220919221229457" style="zoom:50%;" / loading="lazy"> <span class="math display">\[\left[\begin{array}{l}x^{\prime} \\y^{\prime}\end{array}\right]=\left[\begin{array}{ll}\cos \theta & -\sin \theta \\\sin \theta & \cos \theta\end{array}\right]\left[\begin{array}{l}x \\y\end{array}\right]\]</span></p><h3 id="仿射变换">仿射变换</h3><h4 id="齐次坐标">齐次坐标</h4><blockquote><p>由 August Ferdinand M ö bius 提出的齐次坐标(又称投影坐标,homogeneous coordinates),使图形和几何学的计算在投影空间中成为可能。齐次坐标是用 <span class="math inline">\(N+1\)</span> 个数来表示 <span class="math inline">\(N\)</span> 维坐标的一种方式</p></blockquote><p>要制作齐次坐标,我们只需在现有坐标中增加一个额外的变量 <span class="math inline">\(w\)</span>。具体而言,在齐次坐标中我们把 <strong>点</strong> 和 <strong>向量</strong> 的表示区别开</p><ul><li>将原本的向量<span class="math inline">\((x, y)^T\)</span>表示为<span class="math inline">\((x, y,0)^T\)</span></li><li>点则为<span class="math inline">\((x, y,1)^T\)</span>的形式</li></ul><p>说了这么多,所以为什么要引入 <strong>齐次坐标</strong> 呢?</p><blockquote><p>因为懒是一个一个一个美德。 by 闫令琪</p></blockquote><p>你可以发现,上述线性变化矩阵中并没有熟悉的平移 Translation 的身影</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220919223311.png" alt="image-20220919223311205" style="zoom:50%;" / loading="lazy"></p><p>事实上,他也确实不能写作所谓线性变换的形式(因为原点的位置已经被改变了,而线性变化在几何上直观的特点就是:<strong>原点不变</strong> 以及 <strong>直线在变换后依然为直线</strong>),所以我们不得不写成以下形式 <span class="math display">\[\left[\begin{array}{l}x^{\prime} \\y^{\prime}\end{array}\right]=\left[\begin{array}{ll}a & b \\c & d\end{array}\right]\left[\begin{array}{l}x \\y\end{array}\right]+\left[\begin{array}{l}t_x \\t_y\end{array}\right]\]</span> 这种不统一的形式,显然令人不喜,于是乎引入了 <strong>齐次坐标</strong> 来解决平移的问题。又因为向量具有 <strong>平移不变性</strong>,所以我们将其的 <span class="math inline">\(w\)</span> 设置为 <span class="math inline">\(0\)</span>。事实上,这种设置也确实有更多的实际意义,比如 <span class="math display">\[vector + vector = vector\\point - point = vector\\point + vector = point\\point + point = point\]</span> 注意这里的点之间的加法运算,实际表示的是两点的 <strong>中点</strong>。因为点的完整齐次坐标定义为 <span class="math inline">\((\frac{x}{w},\frac{y}{w},1)^T,\ w\neq 0\)</span></p><h4 id="仿射变换-1">仿射变换</h4><p>通过引入了齐次坐标,我们可以将之前比较丑陋的形式改写为 <strong>旋转缩放平移</strong> 三位一体的仿射变换(Affine transformation)形式 <span class="math display">\[\left(\begin{array}{l}x^{\prime} \\y^{\prime} \\1\end{array}\right)=\left(\begin{array}{llc}a & b & t_x \\c & d & t_y \\0 & 0 & 1\end{array}\right) \cdot\left(\begin{array}{l}x \\y \\1\end{array}\right)\]</span> 我们仍然可以用基向量的方式进行理解,<span class="math inline">\(\hat x', \hat y'\)</span> 是向量所以其为 <span class="math inline">\((a,c,0)^T,\ (b,d,0)^T\)</span>,最后一个理解为 <strong>变换后的原点坐标</strong> <span class="math inline">\((t_x,t_y,1)^T\)</span>,那么我们就可以用仿射变换轻松地表示之前的旋转缩放平移</p><p><span class="math display">\[\mathbf{S}\left(s_x, s_y\right)=\left(\begin{array}{ccc}s_x & 0 & 0 \\0 & s_y & 0 \\0 & 0 & 1\end{array}\right)\\ \\\]</span></p><p><span class="math display">\[\mathbf{R}(\alpha)=\left(\begin{array}{ccc}\cos \alpha & -\sin \alpha & 0 \\\sin \alpha & \cos \alpha & 0 \\0 & 0 & 1\end{array}\right)\\ \\\]</span></p><p><span class="math display">\[\mathbf{T}\left(t_x, t_y\right)=\left(\begin{array}{ccc}1 & 0 & t_x \\0 & 1 & t_y \\0 & 0 & 1\end{array}\right)\]</span></p><h3 id="d-变换">3D 变换</h3><p>三维的情况基本与二维没有出入,除了旋转有个要注意的点。同样的,定义三维的齐次坐标</p><ul><li>点:<span class="math inline">\((x/w, y/w, z/w,1)^T ,\ w \neq 0\)</span></li><li>向量:<span class="math inline">\((x, y, z,0)^T\)</span></li></ul><p>其仿射变换如下: <span class="math display">\[\left(\begin{array}{c}x^{\prime} \\y^{\prime} \\z^{\prime} \\1\end{array}\right)=\left(\begin{array}{lllc}a & b & c & t_x \\d & e & f & t_y \\g & h & i & t_z \\0 & 0 & 0 & 1\end{array}\right) \cdot\left(\begin{array}{l}x \\y \\z \\1\end{array}\right)\]</span></p><h4 id="缩放">缩放</h4><p><span class="math display">\[\mathbf{S}\left(s_x, s_y, s_z\right)=\left(\begin{array}{cccc}s_x & 0 & 0 & 0 \\0 & s_y & 0 & 0 \\0 & 0 & s_z & 0 \\0 & 0 & 0 & 1\end{array}\right)\]</span></p><h4 id="平移">平移</h4><p><span class="math display">\[\mathbf{T}\left(t_x, t_y, t_z\right)=\left(\begin{array}{cccc}1 & 0 & 0 & t_x \\0 & 1 & 0 & t_y \\0 & 0 & 1 & t_z \\0 & 0 & 0 & 1\end{array}\right)\]</span></p><h4 id="旋转绕着某个轴">旋转(绕着某个轴)</h4><p><span class="math display">\[\begin{aligned}&\mathbf{R}_x(\alpha)=\left(\begin{array}{cccc}1 & 0 & 0 & 0 \\0 & \cos \alpha & -\sin \alpha & 0 \\0 & \sin \alpha & \cos \alpha & 0 \\0 & 0 & 0 & 1\end{array}\right) \\&\mathbf{R}_y(\alpha)=\left(\begin{array}{cccc}\cos \alpha & 0 & \sin \alpha & 0 \\0 & 1 & 0 & 0 \\-\sin \alpha & 0 & \cos \alpha & 0 \\0 & 0 & 0 & 1\end{array}\right) \\&\mathbf{R}_z(\alpha)=\left(\begin{array}{cccc}\cos \alpha & -\sin \alpha & 0 & 0 \\\sin \alpha & \cos \alpha & 0 & 0 \\0 & 0 & 1 & 0 \\0 & 0 & 0 & 1\end{array}\right)\end{aligned}\]</span></p><ul><li>不难发现,绕着<span class="math inline">\(Y\)</span>轴旋转的结果有些奇怪,这是因为<span class="math inline">\(X\)</span>轴基向量和<span class="math inline">\(Z\)</span>轴基向量的叉乘为<span class="math inline">\(Y\)</span>轴负向的向量,那么所有的旋转角度都会相当于变为原来的 <strong>负值</strong>,从而才会产生这样的差异</li></ul><h4 id="旋转rodriguesrotation-formula">旋转(Rodrigues'Rotation Formula)</h4><p>绕着转轴 <span class="math inline">\(n\)</span> 旋转 <span class="math inline">\(\alpha\)</span> 角度,<a href="https://www.cnblogs.com/wtyuan/p/12324495.html">推导过程</a>:cry: <span class="math display">\[R(n, \alpha)=\cos (\alpha) E+(1-\cos (\alpha)) n n^T+\sin (\alpha)\left(\begin{array}{ccc}0 & -n_z & n_y \\n_z & 0 & -n_x \\-n_y & n_x & 0\end{array}\right)\]</span></p><h2 id="视图相机变换">视图/相机变换</h2><p>想象我们拍照的过程,首先放置几个人物(模型变换 M),然后给定相机位置(相机变换 V),最后按下快门保存为一张二维的照片(投影变换 P),这就是所谓的 <strong>MVP Transformation</strong></p><h3 id="定义相机属性">定义相机属性</h3><ol type="1"><li>位置(postion): <span class="math inline">\(\vec{e}\)</span></li><li>视线方向(Look-at / gaze direction):<span class="math inline">\(\hat g\)</span></li><li>垂直方向(Up direction):<span class="math inline">\(\hat t\)</span></li></ol><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220919232151.png" alt="image-20220919232151055" style="zoom:67%;" / loading="lazy"></p><ul><li><p>事实上将你的头视作摄像机,改变位置很好理解,走两步就行;改变视线方向也很好理解,即让你的视线瞄准一个坐标为<span class="math inline">\((x, y, z)^T\)</span>的物体即可;最后一个垂直方向,是类似法线(呆毛)的概念,你可以通过 <strong>歪头</strong> 这一行为更改</p></li><li><p>约定初始状态,也为了后续表示的方便,相机 <strong>永远</strong> 位于原点,永远视线朝向<span class="math inline">\(-Z\)</span>,永远上方向为<span class="math inline">\(Y\)</span></p></li></ul><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220919234134.png" alt="image-20220919234134279" style="zoom: 67%;" / loading="lazy"></p><p>那么现在的问题就变为,如何将一个由 <span class="math inline">\((\vec{e},\hat g, \hat t)\)</span> 表示的相机,<strong>标准化</strong> 为上述的初始状态呢?不难想到应该由以下几步:</p><ol type="1"><li>将其位置 <span class="math inline">\(\vec{e}\)</span> 移动到原点(平移变换)</li><li>将 $g $ 朝向 <span class="math inline">\(-Z\)</span>(旋转变换)</li><li>将 $t $ 朝向 <span class="math inline">\(Y\)</span>(旋转变换)</li><li>令 $g t $ 朝向 <span class="math inline">\(X\)</span>(旋转变化)</li></ol><p>总体来说,先平移后旋转(先右后左):<span class="math inline">\(M_v = R_vT_v\)</span></p><p>这样一步步写,无疑是很痛苦的。所以我们考虑其逆矩阵(因为旋转矩阵是正交矩阵,所以其逆矩阵就是它的转置矩阵)</p><p>首先是平移(这步没有必要逆矩阵) <span class="math display">\[T_v =\left[\begin{array}{cccc}1 & 0 & 0 & -x_e \\0 & 1 & 0 & -y_e \\0 & 0 & 1 & -z_e \\0 & 0 & 0 & 1\end{array}\right]\]</span> 然后是旋转,这里我们逆向考虑把要求的各个方向的 <strong>基向量</strong> 转到 <span class="math inline">\(\hat g,\ \hat t,\ \hat g \times \hat t\)</span> 即可得到逆旋转矩阵,然后转置即可得到旋转矩阵 <span class="math display">\[\begin{aligned}R_{\text {v }}^{-1} &=\left[\begin{array}{llll}x_{\hat{g} \times \hat{t}} & x_t & x_{-g} & 0 \\y_{\hat{g} \times \hat{t}} & y_t & y_{-g} & 0 \\z_{\hat{g} \times \hat{t}} & z_t & z_{-g} & 0 \\0 & 0 & 0 & 1\end{array}\right] \\R_{\text {v }} &=\left[\begin{array}{cccc}x_{\hat{g} \times \hat{t}} & y_{\hat{g} \times \hat{t}} & z_{\hat{g} \times \hat{t}} & 0 \\x_t & y_t & z_t & 0 \\x_{-g} & y_{-g} & z_{-g} & 0 \\0 & 0 & 0 & 1\end{array}\right]\end{aligned}\]</span></p><h2 id="投影变换">投影变换</h2><h3 id="正交投影">正交投影</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220920124755.jpeg" alt="img" style="zoom: 80%;" / loading="lazy"></p><p>上图显示一个简单的正交投影的三个不同视图,正交投影可以简单理解成</p><ol type="1"><li>摄像机看向 <span class="math inline">\(-Z\)</span> 方向,上方向为 <span class="math inline">\(Y\)</span></li><li>将 <span class="math inline">\(Z\)</span> 设置为 0</li><li>平移并缩放物体至区域 <span class="math inline">\([-1,1]^2\)</span></li></ol><p>但在实际操作中,我们一般用一个立方体以描述正交投影的变换矩阵(Orthographic Projection),以六元组 <span class="math inline">\((l,r,b,t,n,f)\)</span> 表示其左,右,底,顶,近,远平面</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220920174659.jpeg" alt="img" style="zoom:80%;" / loading="lazy"></p><ul><li>注意<span class="math inline">\(Z\)</span>值,这里<span class="math inline">\(z_{far} < z_{near}\)</span></li></ul><p>那我们的目的就是将其缩放到变为一个标准立方体,不难想到也是先平移再缩放 <span class="math display">\[\begin{aligned}{M}_{ortho} ={M}_s M_t &=\left(\begin{array}{cccc}\frac{2}{r-l} & 0 & 0 & 0 \\0 & \frac{2}{t-b} & 0 & 0 \\0 & 0 & \frac{2}{n-f} & 0 \\0 & 0 & 0 & 1\end{array}\right)\left(\begin{array}{cccc}1 & 0 & 0 & -\frac{l+r}{2} \\0 & 1 & 0 & -\frac{t+b}{2} \\0 & 0 & 1 & -\frac{n+f}{2} \\0 & 0 & 0 & 1\end{array}\right) \\&=\left(\begin{array}{cccc}\frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\0 & 0 & \frac{2}{n-f} & -\frac{n+f}{n-f} \\0 & 0 & 0 & 1\end{array}\right) .\end{aligned}\]</span></p><h3 id="透视投影">透视投影</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220920180456.png" alt="image-20220920180456183" style="zoom: 67%;" / loading="lazy"></p><p>想象把左边的平截头体(Frustum)<strong>挤压</strong> 成右边的立方体,然后再做一次 <strong>正交投影</strong> 即可。显然在 <strong>挤压</strong> 的过程中满足以下条件</p><ol type="1"><li>近平面所有点不变</li><li>远平面所有点坐标 z 值不变</li><li>远平面的中心点坐标值不变</li></ol><p>那么问题就变为了如何 <strong>挤压</strong>,这里用矩阵 <span class="math inline">\(M_{persp \to orho}\)</span> 表示</p><p>首先来看一个纵切面</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220920181358.png" alt="image-20220920181358915" style="zoom: 80%;" / loading="lazy"></p><p>通过小学二年级的相似知识,不难得出 <span class="math inline">\(\dfrac{x'}{x} =\dfrac{y'}{y} = \dfrac{n}{z}\)</span></p><p>即 <span class="math inline">\(y' = \frac{n}{z} y,\ x' = \frac{n}{z}x\)</span>,写成矩阵变换的形式</p><p><span class="math display">\[M_{p \rightarrow o}^{(4 \times 4)}\left(\begin{array}{c}x \\y \\z \\1\end{array}\right)==\left(\begin{array}{c}n x / z \\n y / z \\\text { unknown } \\1\end{array}\right)==\left(\begin{array}{c}n x \\n y \\\text { still unknown } \\z\end{array}\right)\]</span></p><p>如果将矩阵乘法展开第一行,第二行以及最后一行:</p><p><span class="math display">\[Ax+By+Cz+D = nx \\Ex+Fy+Gz+H = ny \\Mx+Ny+Oz+P = z\]</span></p><p>不难发现应得到:<span class="math inline">\(A=F=n,\ O = 1\)</span> 其余项为 0,那么至此 <span class="math inline">\(M_{p\to o}\)</span> 已经可以写出一大半了,只有第三行未知</p><p>这时想到之前的 3 个约束条件:</p><ol type="1"><li><p>近平面所有点不变</p><p><span class="math inline">\((x,y,n,1)^T\)</span> 经过 <span class="math inline">\(M_{p\to o}\)</span> 变换后应该还是为本身才行 <span class="math display">\[\left(\begin{array}{cccc}n & 0 & 0 & 0 \\0 &n & 0 & 0 \\I & J & K & L \\0 & 0 & 1 & 0\end{array}\right) .\left(\begin{array}{c}x \\y \\n \\1 \end{array}\right)= \left(\begin{array}{c}x \\y \\n \\1 \end{array}\right)\]</span> 很明显,这里只有当 $n =1 $ 时才成立,这显然有问题。所以我们这里选择把结果 <span class="math inline">\((x,y,n,1)^T*n\)</span>,这样 <span class="math display">\[\left(\begin{array}{cccc}n & 0 & 0 & 0 \\0 &n & 0 & 0 \\I & J & K & L \\0 & 0 & 1 & 0\end{array}\right) .\left(\begin{array}{c}x \\y \\n \\1 \end{array}\right)= \left(\begin{array}{c}nx \\ny \\n^2 \\n \end{array}\right)\]</span> 那么就可以开始求取第三行了 <span class="math display">\[Ix+Jy+Kn+L = n^2\]</span> 很明显,<span class="math inline">\(I = J=0,\ Kn+ L = n^2\)</span></p></li><li><p>远平面中心点坐标值不变</p><p><span class="math inline">\((0,0,f,1)\)</span> 经过 <span class="math inline">\(M_{p\to o}\)</span> 变换后应该还是为本身才行,同样的,为了满足第四行选择 <span class="math inline">\((0,0,f,1)^T*f\)</span> <span class="math display">\[\left(\begin{array}{cccc}n & 0 & 0 & 0 \\0 &n & 0 & 0 \\0 & 0 & K & L \\0 & 0 & 1 & 0\end{array}\right) .\left(\begin{array}{c}0 \\0 \\f \\1 \end{array}\right)= \left(\begin{array}{c}0 \\0 \\f \\1 \end{array}\right)= =\left(\begin{array}{c}0 \\0 \\f^2 \\f \end{array}\right)\]</span> 那么就可以得到 <span class="math inline">\(Kf+L = f^2\)</span></p></li></ol><p>联立上面两个式子 <span class="math display">\[\begin{cases} Kn+ L &= n^2\\ Kf+ L &= f^2\end{cases}\]</span> 解得 <span class="math inline">\(K = n+f,\ D = -nf\)</span></p>所以就可以得到投影向正交变换的矩阵 <span class="math display">\[\left(\begin{array}{cccc}n & 0 & 0 & 0 \\0 &n & 0 & 0 \\0 & 0 & n+f & -nf \\0 & 0 & 1 & 0\end{array}\right)\]</span> 那么,最终的透视投影矩阵即为 $$<span class="math display">\[\begin{aligned}M_{persp} &= M_{ortho}M_{p\to o} \\&=\left(\begin{array}{cccc}\frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\0 & 0 & \frac{2}{n-f} & -\frac{n+f}{n-f} \\0 & 0 & 0 & 1\end{array}\right) .\left(\begin{array}{cccc}n & 0 & 0 & 0 \\0 &n & 0 & 0 \\0 & 0 & n+f & -nf \\0 & 0 & 1 & 0\end{array}\right)\\&=\left(\begin{array}{cccc}\frac{2n}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\0 & \frac{2n}{t-b} & -\frac{t+b}{t-b} & 0 \\0 & 0 & \frac{n+f}{n-f} & -\frac{2fn}{n-f} \\0 & 0 & 1 & 0\end{array}\right) \\&\iff\left(\begin{array}{cccc}\frac{2n}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\0 & \frac{2n}{t-b} & \frac{t+b}{t-b} & 0 \\0 & 0 & \frac{n+f}{f-n} & \frac{2fn}{n-f} \\0 & 0 & -1 & 0\end{array}\right) \end{aligned}\]</span><p>$$</p><h3 id="透视投影补充">透视投影补充</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220922212930.png" alt="image-20220922212930307" style="zoom:80%;" / loading="lazy"></p><p>上文提到,关于Frustum的定义可以用六元组<span class="math inline">\((l,r,b,t,n,f)\)</span>定义,人们也通常选择使用<span class="math inline">\(FOV\)</span>,即field of view视角来加以描述,其又分为水平<span class="math inline">\(XFOV\)</span>和竖直<span class="math inline">\(YFOV\)</span>,不难想象<span class="math inline">\(FOV\)</span>越大,相机能观测到的范围就越大;另一个概念是宽高比Aspect ratio,正如其字面意思就是屏幕的比例。</p><p>选取一个竖直截面,不难推导出竖直方向的视角公式</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220922213850.png" alt="image-20220922213850774" style="zoom:80%;" / loading="lazy"> <span class="math display">\[\begin{equation}\begin{split} &\tan \frac{fovY}{2} = \frac{t}{ n}\\&\Rightarrow fovY = 2\arctan(\frac{t}{ n})\end{split}\end{equation}\]</span> 另一个参数宽高比则为: <span class="math display">\[\begin{equation}\begin{split} aspect &= \dfrac{width}{height} \\&= \dfrac{2r}{2t}\\&= \dfrac{r}{t}\end{split}\end{equation}\]</span> 接着我们选择使用视角系统下的参数(fov, aspect, far, near)来重写透视投影矩阵<span class="math inline">\(M_{persp}\)</span></p><p>显然:<span class="math inline">\(n = near\)</span>,<span class="math inline">\(f = far\)</span></p><p>根据上面的解三角形: <span class="math display">\[\begin{equation}\begin{split} t &= near\cdot \tan \frac{fovY}{2}\\b &= -t = -near\cdot \tan \frac{fovY}{2}\\r &= aspect\cdot t = aspect\cdot near\cdot \tan \frac{fovY}{2}\\l &= -r = -aspect\cdot near\cdot \tan \frac{fovY}{2}\end{split}\end{equation}\]</span></p><p>然后带入并正交投影矩阵<span class="math inline">\(M_{ortho}\)</span></p><p><span class="math display">\[\begin{aligned}{M}_{ortho} ={M}_s M_t &=\left(\begin{array}{cccc}\frac{\cot fovY}{2aspect\cdot near} & 0 & 0 & 0 \\0 & \frac{\cot fovY}{2near} & 0 & 0 \\0 & 0 & \frac{2}{near-far} & 0 \\0 & 0 & 0 & 1\end{array}\right)\left(\begin{array}{cccc}1 & 0 & 0 & 0 \\0 & 1 & 0 & 0 \\0 & 0 & 1 & -\frac{near+far}{2} \\0 & 0 & 0 & 1\end{array}\right) \\&=\left(\begin{array}{cccc} \frac{\cot fovY}{2aspect\cdot near} & 0 & 0 & 0 \\0 & \frac{\cot fovY}{2near} & 0 & 0 \\0 & 0 & \frac{2}{near-far} &-\frac{near+far}{near-far} \\0 & 0 & 0 & 1\end{array}\right) \end{aligned}\]</span></p><p>然后<span class="math inline">\(M_{perspec}\)</span></p>$$<span class="math display">\[\begin{aligned}M_{persp} &= M_{ortho}M_{p\to o} \\&=\left(\begin{array}{cccc} \frac{\cot fovY}{2aspect\cdot near} & 0 & 0 & 0 \\0 & \frac{\cot fovY}{2near} & 0 & 0 \\0 & 0 & \frac{2}{near-far} &-\frac{near+far}{near-far} \\0 & 0 & 0 & 1\end{array}\right) .\left(\begin{array}{cccc}near & 0 & 0 & 0 \\0 &near & 0 & 0 \\0 & 0 & near+far & -near\cdot far \\0 & 0 & 1 & 0\end{array}\right)\\&=\left(\begin{array}{cccc}\frac{\cot fovY}{2aspect} & 0 & 0 & 0 \\0 & \frac{\cot fovY}{2} & 0 & 0 \\0 & 0 & \frac{near+far}{near-far} &-\frac{2*near\cdot far}{near-far} \\0 & 0 & 1 & 0\end{array}\right) \\\end{aligned}\]</span><p>$$</p><h2 id="作业1">作业1</h2><blockquote><p>到目前为止,我们已经学习了如何使用矩阵变换来排列二维或三维空间中的对象。所以现在是时候通过实现一些简单的变换矩阵来获得一些实际经验了。在接下来的三次作业中,我们将要求你去模拟一个基于CPU 的光栅化渲染器的简化版本。</p><p>本次作业的任务是填写一个旋转矩阵和一个透视投影矩阵。</p><p>给定三维下三个点<span class="math inline">\(v0(2.0, 0.0,−2.0), v1(0.0, 2.0,−2.0), v2(−2.0, 0.0,−2.0)\)</span>,你需要将这三个点的坐标变换为屏幕坐标,并在屏幕上绘制出对应的线框三角形(在代码框架中,我们已经提供了draw_triangle 函数,所以你只需要去构建变换矩阵即可)。简而言之,我们需要进行模型、视图、投影、视口等变换来将三角形显示在屏幕上。在提供的代码框架中,我们留下了模型变换和投影变换的部分给你去完成。</p></blockquote><pre class="language-cpp" data-language="cpp"><code class="language-cpp"><span class="token keyword">constexpr</span> <span class="token keyword">double</span> MY_PI <span class="token operator">=</span> <span class="token number">3.1415926</span><span class="token punctuation">;</span><span class="token keyword">inline</span> <span class="token keyword">double</span> <span class="token function">deg2red</span><span class="token punctuation">(</span><span class="token keyword">double</span> deg<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> deg <span class="token operator">*</span> MY_PI <span class="token operator">/</span> <span class="token number">180</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token keyword">inline</span> <span class="token keyword">double</span> <span class="token function">cot</span><span class="token punctuation">(</span><span class="token keyword">double</span> X<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token number">1.0</span> <span class="token operator">/</span> <span class="token function">tan</span><span class="token punctuation">(</span>X<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f <span class="token function">get_model_matrix</span><span class="token punctuation">(</span><span class="token keyword">float</span> rotation_angle<span class="token punctuation">)</span><span class="token punctuation">{</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f model <span class="token operator">=</span> Eigen<span class="token double-colon punctuation">::</span><span class="token class-name">Matrix4f</span><span class="token double-colon punctuation">::</span><span class="token function">Identity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// TODO: Implement this function</span><span class="token comment">// Create the model matrix for rotating the triangle around the Z axis.</span><span class="token comment">// Then return it.</span><span class="token comment">//先将角度换成弧度</span>rotation_angle <span class="token operator">=</span> <span class="token function">deg2red</span><span class="token punctuation">(</span>rotation_angle<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//按照三维沿Z轴旋转写即可</span>model <span class="token operator"><<</span> <span class="token function">cos</span><span class="token punctuation">(</span>rotation_angle<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token function">sin</span><span class="token punctuation">(</span>rotation_angle<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token function">sin</span><span class="token punctuation">(</span>rotation_angle<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">cos</span><span class="token punctuation">(</span>rotation_angle<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token keyword">return</span> model<span class="token punctuation">;</span><span class="token punctuation">}</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f <span class="token function">get_projection_matrix</span><span class="token punctuation">(</span><span class="token keyword">float</span> eye_fov<span class="token punctuation">,</span> <span class="token keyword">float</span> aspect_ratio<span class="token punctuation">,</span> <span class="token keyword">float</span> zNear<span class="token punctuation">,</span> <span class="token keyword">float</span> zFar<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// TODO: Copy-paste your implementation from the previous assignment.</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f projection<span class="token punctuation">;</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f scale <span class="token operator">=</span> Eigen<span class="token double-colon punctuation">::</span><span class="token class-name">Matrix4f</span><span class="token double-colon punctuation">::</span><span class="token function">Identity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f translate <span class="token operator">=</span> Eigen<span class="token double-colon punctuation">::</span><span class="token class-name">Matrix4f</span><span class="token double-colon punctuation">::</span><span class="token function">Identity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f persp2ortho <span class="token operator">=</span> Eigen<span class="token double-colon punctuation">::</span><span class="token class-name">Matrix4f</span><span class="token double-colon punctuation">::</span><span class="token function">Identity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>eye_fov <span class="token operator">=</span> <span class="token function">deg2red</span><span class="token punctuation">(</span>eye_fov <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//注意这里弧度转角度的时候为其一半</span><span class="token comment">//Eigen::Matrix4f ortho = scale * translate;</span><span class="token comment">//projection = ortho * persp2ortho;</span><span class="token comment">// </span><span class="token comment">//方法一:定义六元组走第一个投影矩阵</span><span class="token comment">//float n = zNear;</span><span class="token comment">//float f = zFar;</span><span class="token comment">//float t = zNear * tan(eye_fov / 2);</span><span class="token comment">//float b = -t;</span><span class="token comment">//float r = aspect_ratio * t;</span><span class="token comment">//float l = -r;</span><span class="token comment">//方法二:直接使用视角投影矩阵</span>scale <span class="token operator"><<</span><span class="token function">cot</span><span class="token punctuation">(</span>eye_fov<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">*</span> aspect_ratio <span class="token operator">*</span> zNear<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token function">cot</span><span class="token punctuation">(</span>eye_fov<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token punctuation">(</span><span class="token number">2</span> <span class="token operator">*</span> zNear<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">2.0</span> <span class="token operator">/</span> <span class="token punctuation">(</span>zNear <span class="token operator">-</span> zFar<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">;</span>translate <span class="token operator"><<</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token punctuation">(</span>zNear <span class="token operator">+</span> zFar<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">;</span>persp2ortho <span class="token operator"><<</span>zNear<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> zNear<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> zNear <span class="token operator">+</span> zFar<span class="token punctuation">,</span> <span class="token operator">-</span>zNear <span class="token operator">*</span> zFar<span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">;</span>Eigen<span class="token double-colon punctuation">::</span>Matrix4f ortho <span class="token operator">=</span> scale <span class="token operator">*</span> translate<span class="token punctuation">;</span>projection <span class="token operator">=</span> ortho <span class="token operator">*</span> persp2ortho<span class="token punctuation">;</span><span class="token comment">//方法3:一步到位</span><span class="token comment">//projection <<</span><span class="token comment">//cot(eye_fov) / (2 * aspect_ratio ), 0, 0, 0,</span><span class="token comment">//0, cot(eye_fov)/2 , 0, 0,</span><span class="token comment">//0, 0, zNear + zFar / (zNear - zFar), -2.0 * zNear * zFar / (zNear - zFar),</span><span class="token comment">//0, 0, 1, 0;</span><span class="token keyword">return</span> projection<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>效果如下:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220926150523.png" alt="image-20220926150515854" style="zoom:67%;" / loading="lazy"></p><p>可以使用<code>A</code>和<code>D</code>按键自由旋转</p><h2 id="参考">参考</h2><p><a href="https://zhuanlan.zhihu.com/p/122411512?utm_source=qq&utm_medium=social&utm_oi=605668290971045888">推导投影矩阵 - 知乎</a></p><p><a href="https://zhuanlan.zhihu.com/p/258437902">什么是齐次坐标? - 知乎</a></p><p><a href="https://zhuanlan.zhihu.com/p/392216888">实时渲染第四版 4.6 投影变换 - 知乎</a></p><p><a href="https://www.wgqing.com/%E9%80%8F%E8%A7%86%E6%8A%95%E5%BD%B1%E5%8F%98%E6%8D%A2%E6%8E%A8%E5%AF%BC/">透视投影(Perspective Projection)变换推导过程 - codingriver blog</a></p>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>本文是对 <a href="https://www.bilibili.com/video/BV1X7411F744?p=3">Game101</a> L3~L4 的笔记,阅读前请先保证对线性代数几何意义有着较为直观的理解,推荐配合食用 <a href="https://www.bilibili.com/video/BV1ys411472E">3Blue1Brown-线性代数的本质</a></p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220920190945.jpeg" alt="img" style="zoom:80%;" /></p></summary>
<category term="Games101笔记" scheme="http://lapras.xyz/categories/Games101%E7%AC%94%E8%AE%B0/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="计算机图形学" scheme="http://lapras.xyz/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9B%BE%E5%BD%A2%E5%AD%A6/"/>
</entry>
<entry>
<title>Linux中的文件权限</title>
<link href="http://lapras.xyz/2022/09/17/c66f2510.html"/>
<id>http://lapras.xyz/2022/09/17/c66f2510.html</id>
<published>2022-09-17T04:07:57.000Z</published>
<updated>2022-09-23T04:29:05.757Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>Turn My Body into Fire!</p><p>Proposing a toast to David!</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220916222848.jpeg" alt="img" style="zoom: 45%;" / loading="lazy"></p><span id="more"></span><h2 id="文件权限">文件权限</h2><p>在阅读之前,记住一件事,Linux 中的权限一般是指文件和用户(用户组)的对应关系,这会很大程度上帮助你理解</p><h3 id="用户与用户组">用户与用户组</h3><p>Linux 的服务器属性注定了会有多用户使用一台机器的需求,所以为了用户之间尽可能不要互相干扰,系统管理员必须做好用户管理,这又是另一个专业的问题了,这里不过多赘述。对于权限部分的知识,只需要知道:</p><p>每个用户都有唯一标识符 <code>UID</code>,其与用户名是一一对应关系。系统会在登录的时候通过查询 <code>/etc/passwd</code> 文件来确定用户的 <code>UID</code> 和主目录。该文件的每一行都描述了一个用户,包含由冒号分隔的 7 个部分</p><pre class="language-none"><code class="language-none">root:x:0:0:root:/root:/bin/bash</code></pre><p>从左到右依次是:登录名:加密密码的预留位置:<code>UID</code>:<code>GID</code>:[GECOS 信息](可选项):主目录:登录 <code>shell</code></p><p>Linux 将用户分为三种:超级用户、虚拟用户以及普通用户。超级用户自然就是 root,其 <code>UID</code> 为固定的 0;虚拟用户是为了满足系统进程对文件资源的访问控制而建立的,所以不能用来登录,比如各种 web 服务,daemon,ftp 等等,其 <code>UID</code> 为 1~499</p><p>Linux 中,每个用户都可以拥有多个 <strong>用户组</strong>,但必须有一个作为主用户组,剩余的作为附加用户组,属于某用户组的用户对于某些文件拥有相同的权限。与 <code>/etc/passwd</code> 类似,用户组的信息存放在 <code>/etc/group</code> 中,其包含了每个组的名称以及每个组的成员列表。每一行包含四个字段:组名,加密后的密码,<code>GID</code>,组成员列表</p><blockquote><p>以下是一些更改用户及用户组的命令,但对于本节内容并不重要,所以不多赘述</p><p><code>groupadd</code> 添加用户组,<code>groupdel</code> 删除用户组,<code>groupmod</code> 修改用户组</p><p><code>passwd</code> 修改用户登录密码,<code>adduser</code> 添加用户,<code>userdel</code> 删除用户,<code>usermod</code> 修改用户</p></blockquote><p>在用户和用户组之外,还有一个 <strong>其他用户</strong> 的概念。这个很好理解,比如一个用户张三,其主用户组是张三之家,那么在张三创建的文件看来,隔壁老王既不是张三,也不是张三之家的成员,那么隔壁老王就是 <strong>其他用户</strong></p><h3 id="文件属性">文件属性</h3><p>首先,还是从 <code>ls -l</code> 说起吧,他会以长列表(详情)的方式展示当前目录,这里截取其中一条用以说明</p><pre class="language-none"><code class="language-none">drwxr-xr-x 3 root root 4096 Sep 10 17:43 postgres/</code></pre><ul><li><p>第一栏的第一个字符 <strong>d</strong>,表示这个文件是一个 <strong>目录</strong></p><blockquote><p><strong>-</strong> 表示普通文件</p><p><strong>d</strong> 表示目录,通过 <code>mkdir</code> 创建,通过 <code>rmdir</code> 删除</p><p><strong>l</strong> 表示链接文件,通过 <code>ln -s</code> 创建</p><p><strong>b</strong> 表示块设备文件,例如可供储存的设备,通过 <code>mknod</code> 创建</p><p><strong>c</strong> 表示字符设备文件,例如键盘、鼠标,通过 <code>mknod</code> 创建</p><p><strong>s</strong> 表示本地域套接字,通过 <code>socket</code> 系统调用</p><p><strong>p</strong> 表示具名管道,通过 <code>mknod</code> 创建</p></blockquote></li><li><p>接下来的字符中,以三个为一组,且均为 <code>rwx</code> 的三个参数的组合。其中,<code>r</code> 代表可读(read)、<code>w</code> 代表可写(write)、<code>x</code> 代表可执行(execute)。 没有对应权限,就会出现减号 <code>-</code>。三组 <code>rwx</code> 依次代表了所属用户 user,所属用户组 group 和其他用户 other 的访问权限</p></li><li><p>为了简单起见,我们用三位 8 进制数表示这三组权限,其中 4 表示 <code>r</code>,2 表示 <code>w</code>,1 表示 <code>x</code>,然后每组的权限相加即可,比如 <code>rwxrwxrwx</code> 就可以表示为 777,反之 <code>---------</code> 表示为 000</p></li><li><p>第二栏的数字表示有多少文件名链接到该节点,这部分涉及了 Linux 的 <code>i-node</code> 设计,这里简单地理解为有多少文件可以直接访问到该文件即可</p></li><li><p>第三栏和第四栏分别表示该文件的所属用户和所属用户组,初始情况下为创建该文件的用户</p></li><li><p>第五栏表示文件大小,单位为 <code>Bytes</code></p></li><li><p>第六栏表示该文件的创建或是最近修改日期</p></li><li><p>第七栏自然就是文件的名称</p></li></ul><h3 id="文件权限的意义">文件权限的意义?</h3><h4 id="文件">文件</h4><p>文件是实际含有数据的地方,包括一般文本文件、数据库内容档、二进制可执行文件等等</p><ul><li>r (read):可读取此一文件的实际内容,如读取文本文件的文字内容等</li><li>w(write):可以编辑、新增或者是修改该文件的内容,<strong>但不含删除该文件</strong></li><li>x (execute):该文件具有可以被系统执行的权限</li></ul><h4 id="目录">目录</h4><p>目录主要的内容在记录文件名清单</p><ul><li><p>r (read contents in directory):</p><p>表示具有 <strong>读取目录结构清单的权限</strong>,所以你具有读取一个目录的权限时,表示你可以使用 <code>ls</code> 命令查询该目录下的文件名数据。</p></li><li><p>w (modify contents of directory):</p><p>表示你具有改动该目录结构清单的权限,具体来说:</p><ul><li>创建新的文件与目录</li><li>删除已经存在的文件与目录(<strong>无视该文件的本身权限要求</strong>,但是可以用下文提到的 Sticky 解决)</li><li>将已存在的文件或目录进行更名</li><li>移动该目录内的文件、目录位置</li></ul></li><li><p>x (access directory):</p><p>目录不可以被执行,目录的 <code>x</code> 代表的是使用者能否使用 <code>cd</code> 命令进入该目录并使之成为工作目录。 所谓的工作目录(work directory)就是指当前所在目录(用 <code>pwd</code> 命令查询)</p></li></ul><p>也许你还感觉比较抽象,接着我们用一个比喻来说明这些目录权限的意义:</p><ol type="1"><li>目录看作带有一个 <strong>半透明</strong> 的抽屉,文件看作在抽屉中的一个个密封的文件袋</li><li>目录的 <code>x</code> 权限决定你能否拉开 <code>cd</code> 这个抽屉;<code>w</code> 权限决定你能否放置,丢弃其中的文件袋;<code>r</code> 权限决定这个抽屉里的 <strong>昏暗的小灯</strong> 是否打开,即你是否能看见(<code>ls</code>)里面文件的 <strong>名称</strong></li><li>为什么叫昏暗的小灯呢?因为如果你使用 <code>ls -l</code>,你会发现除了最后的文件名和第一个文件类型,剩余的信息全部都是 <code>?</code>。可以想象这样一个场景,你在半透明抽屉外面透过内部昏暗的灯光(只拥有 <code>r</code> 权限)勉强能分辨出 <strong>文件类型和文件名称</strong>,但剩余的信息需要你 <strong>拉开抽屉</strong>(拥有 <code>x</code> 权限)才能得到</li><li>但灯光 <code>r</code> 也只是辅助你看到文件名称,文件的存在与否与灯光没有关系。所以当你拥有 <strong>拉开抽屉</strong> 的权限时,只要你 <strong>记得</strong> 在黑暗中(这时 TAB 就无效了)有哪些文件,你仍然可以使用一些命令查看或修改他们(只要你拥有对文件本身的具体权限)</li><li>接着是目录的 <code>w</code> 权限,这很好理解,如果我无法拉开抽屉(没有 <code>x</code> 权限),那我即使拥有 <code>w</code> 权限也不能在其中新建或者删除文件。但一旦张三同时拥有了某个目录的 <code>wx</code> 权限,那就可以获得里面所有文件的生杀大权。比如张三无法查看修改李四放在这个目录的的文件,但他可以直接把这个文件丢掉(<code>rm</code>)</li></ol><table><thead><tr class="header"><th>权限</th><th>文件</th><th>目录</th></tr></thead><tbody><tr class="odd"><td>r(读)</td><td>查看文件内容</td><td>浏览目录内容</td></tr><tr class="even"><td>w(写)</td><td>修改文件内容</td><td>在目录中创建文件或目录</td></tr><tr class="odd"><td>x(执行)</td><td>将文件投入运行</td><td>进入目录</td></tr></tbody></table><h3 id="扩展权限">扩展权限</h3><p>Linux 系统中的基本权限位为 9 位权限,加上 3 位特殊权限位,共 12 位权限。</p><ol type="1"><li><p>SUID(setUID)</p><p>只有 <strong>可以执行的二进制程序</strong> 才能设置 <code>SUID</code> 位,<strong>且用户必须拥有该文件的可执行权限</strong>,当用户在 <strong>执行</strong> 该文件时,用户临时拥有该文件 <strong>所属用户</strong> 的权限,直到程序执行结束。表现为所属用户的 <code>x</code> 变为 <code>S</code>(原本无执行权限)或者 <code>s</code>(原本有执行权限)</p><p>常见的栗子就是 <code>/etc/shadow</code> 文件的权限是 <code>000 root root</code>,而更改密码的命令 <code>/bin/passwd</code> 本质上就是修改这个文件,所以按照道理来说,普通用户无法修改 <code>shadow</code> 文件,也就无法更改密码。但是因为 <code>passwd</code> 的权限设置为 <code>rwsr-xr-x root root</code>,其所属用户部分的 <code>s</code> 表示 <code>passwd</code> 命令拥有 setUID 权限,因此一个普通用户在执行该命令时,会 <strong>临时</strong> 获得 <strong>文件所属用户</strong> 也就是 <strong>root</strong> 的权限!而 root 则是可以为所欲为的!同样的,我们最常使用的 <code>sudo</code> 命令也是利用了这一机制</p></li><li><p>SGID(setGID)</p><p>同样的,只有 <strong>可以执行的二进制程序</strong> 才能设置 <code>SGID</code> 位,<strong>且用户必须拥有该文件的可执行权限</strong>,当用户在 <strong>执行</strong> 该文件时,用户临时拥有该文件 <strong>所属用户组</strong> 的权限,直到程序执行结束。表现为所属用户组的 <code>x</code> 变为 <code>S</code>(原本无执行权限)或者 <code>s</code>(原本有执行权限)</p><p>常见的栗子,对于设定了 SetGID 权限的目录来说,普通用户在此目录中的有效组会变成此目录的所属组,若普通用户对此目录拥有 w 权限时,在目录中新建的文件的默认所属组是这个目录的所属组。</p></li></ol><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220917010335.png" alt="未命名绘图.drawio" style="zoom:80%;" / loading="lazy"></p><blockquote><p>所以这两个特殊权限并不是直接 <strong>分享权限</strong>,而是 <strong>分享角色</strong></p></blockquote><ol start="3" type="1"><li>Sticky</li></ol><p>Sticky BIT 表示的是粘着位,主要是用来避免其他用户对文件的误操作。表现为 <strong>其他用户</strong> 的 <code>x</code> 变为 <code>T</code>(原本无执行权限)或者 <code>t</code>(原本有执行权限) 粘着位目前只对目录有效,普通用户要对该目录拥有 <code>w</code> 和 <code>x</code> 权限,即普通用户可以在此目录拥有写入权限。如果没有粘着位,因为普通用户拥有 <code>w</code> 权限,所以可以删除此目录下所有文件,包括其他用户建立的文件。一但赋予了粘着位,除了 root,目录所属用户,文件所属用户可以删除对应文件,普通用户就算拥有 <code>w</code> 权限也只能删除自己建立的文件,但是不能删除其他用户建立的文件,给予公共文件夹 <code>/tmp</code> 一些安全性</p><p>与 <code>rwx</code> 类似的,我们令 <code>SUID</code> 为 4,<code>GUID</code> 为 2,<code>Sticky</code> 为 1,用一个 8 进制数表示三个扩展权限,并把这个八进制数放置在之前的三组权限前形成 4 个八进制数的表示方法</p><h3 id="改变文件权限">改变文件权限</h3><p>改变文件的权限,需要用到 <code>chmod</code> 命令,其有两种语法形式</p><ol type="1"><li><p>chmod [ugoa] [+-=] [rwxst] 文件列表(空格分隔)</p><blockquote><p><code>u</code> 符号代表当前用户</p><p><code>g</code> 符号代表和当前用户在同一个组的用户,以下简称组用户</p><p><code>o</code> 符号代表其他用户</p><p><code>a</code> 符号代表所有用户</p><p><code>r</code> 符号代表读权限以及八进制数 <code>4</code></p><p><code>w</code> 符号代表写权限以及八进制数 <code>2</code></p><p><code>x</code> 符号代表执行权限以及八进制数 <code>1</code></p><p><code>X</code> 符号代表如果目标文件是可执行文件或目录,可给其设置可执行权限</p><p><code>s</code> 符号代表设置权限 suid 和 sgid,使用权限组合 <code>u+s</code> 设定文件的用户的 ID 位,<code>g+s</code> 设置组用户 ID 位</p><p><code>t</code> 符号代表设置 Sticky,只有目录或文件的所有者才可以删除目录下的文件</p><p><code>+</code> 符号代表添加目标用户相应的权限</p><p><code>-</code> 符号代表删除目标用户相应的权限</p><p><code>=</code> 符号代表添加目标用户相应的权限,<strong>删除未提到的权限</strong></p></blockquote></li><li><p>chmod 八进制权限值 文件列表(空格分隔)</p><blockquote><p>777 代表 <code>rwxrwxrwx</code></p><p>666 代表 <code>rw-rw-rw-</code></p><p>1111 代表 <code>--x--x--t</code></p></blockquote></li></ol><h3 id="权限掩码">权限掩码</h3><p><code>umask</code> 是进程的一个属性,目的是为进程创建的文件或目录定义默认权限,它是进程运行环境的一部分</p><p>Shell 创建的所有子孙都将继承这一属性,用户可通过 <code>umask</code> 命令修改 <code>umask</code> 的值,语法为 <code>umask</code> 八进制权限值</p><ul><li>所谓掩码,即指定哪些权限是没有的</li><li>新建文件,哪怕使用<code>umask</code>指定不掩盖执行权限,Linux还是会取消执行权限。新建目录则不会有这一问题</li></ul><h2 id="参考">参考</h2><p>鸟哥的 Linux 私房菜(第四版)</p><p>Unix/Linux 系统管理技术手册(第五版)</p><p>https://blog.csdn.net/wxbmelisky/article/details/51649343</p>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>Turn My Body into Fire!</p>
<p>Proposing a toast to David!</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220916222848.jpeg" alt="img" style="zoom: 45%;" /></p></summary>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="Linux" scheme="http://lapras.xyz/tags/Linux/"/>
</entry>
<entry>
<title>汇编语言程序设计(一)</title>
<link href="http://lapras.xyz/2022/09/06/fdbc8e77.html"/>
<id>http://lapras.xyz/2022/09/06/fdbc8e77.html</id>
<published>2022-09-06T02:54:52.131Z</published>
<updated>2022-09-06T03:04:44.750Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>中秋节,真不戳</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220906105428.jpg" alt="100953677_p0_master1200" style="zoom:67%;" / loading="lazy"></p><span id="more"></span><p>一些常用名词:</p><ul><li>字长(数据宽度):微处理器一次可以直接处理的二进制数码的 <strong>位数</strong>,它通常取决于微处理器内部 <strong>通用寄存器</strong> 的位数和 <strong>数据总线</strong> 的宽度</li><li>寻址能力:指 CPU 能直接存取数据的 <strong>内存地址</strong> 的范围,它由 CPU 的 <strong>地址总线</strong> 的数目决定</li><li>主频(时钟频率):用来表示微处理器的运行速度,主频越高 ,表明微处理器运行越快,主频的单位是 MHz、GHz</li><li>MIPS(Millions of Instruction Per Second):每秒钟能执行多少百万条指令</li><li>微处理器的集成度:微处理器芯片上集成的晶体管数目</li></ul><h2 id="位微处理器内部结构">16 位微处理器内部结构</h2><blockquote><p>Intel 8086 CPU 与随后推出的 8088 CPU 比较类似。8086CPU 是 16 位微处理器,它有 16 根数据线和 20 根地址线,所以可寻址的地址空间是 <span class="math inline">\(2^{20}B = 1MB\)</span>。8088CPU 的内部寄存器、内部运算部件以及内部操作都是按 16 位设计的,但对外的数据总线只有 8 位,在处理一个 16 位数据时,8088 需要两步操作,因而称 8088 是准 16 位微处理器。后来推出的 80286,它的内部结构除了具备 8086/8088 最基本的功能外,还增加了虚拟存储、特权保护、任务管理等功能,所以支持多用户和多任务系统。</p></blockquote><h3 id="内部结构">内部结构</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/8086/8088%E7%BB%93%E6%9E%84.png" alt="image-20220906091341708" style="zoom:80%;" / loading="lazy"></p><ul><li><strong>总线接口单元</strong> 由段寄存器(<code>CS</code>、<code>DS</code>、<code>SS</code>、<code>ES</code>)、指令指针寄存器(<code>IP</code>)、内部暂存器、指令队列、地址加法器及总线控制电路组成。它的主要作用是负责执行所有的外部总线操作</li><li><strong>执行单元</strong> 由通用寄存器、运算数暂存器、算术逻辑单元(<code>ALU</code>)、标志寄存器(<code>FLAGS</code>)及 <code>EU</code> 控制电路组成。它的主要作用是分析和执行指令</li><li>指令队列主要使 <code>EU</code> 和 <code>BIU</code> 并行工作,取指令操作、分析指令操作重叠进行,从而形成了 <strong>两级指令流水线结构</strong>,减少了 CPU 为取指令而必须等待的时间,提高了 CPU 的利用率,加快了整机运行速度,也降低了对存储器存取速度的要求</li></ul><h3 id="寄存器结构">寄存器结构</h3><h4 id="通用寄存器">通用寄存器</h4><p>8086/8088 微处理器的执行单元中有 8 个 <strong>16 位</strong> 的通用寄存器,这些寄存器都可以存放数据或地址,并能进行 <strong>16 位和 8 位</strong> 的数据运算</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220906092902.png" alt="通用寄存器" style="zoom:67%;" / loading="lazy"></p><ul><li>数据寄存器<ul><li><code>AX</code> 称为累加器(Accumulator)使用频度最高,用于算术、逻辑运算以及与外设传送信息</li><li><code>BX</code> 称为基址寄存器(Basc Address Rcgister)常用作存放存储器地址</li><li><code>CX</code> 称为计数器(Counter)作为循环和串操作等指令中的隐含计数器</li><li><code>DX</code> 称为数据寄存器(Data Register)常用来存放双字长数据的高 16 位,或存放外设端口地址</li><li>这四个数据寄存器可作为两个独立的 <strong>8 位寄存器</strong> 使用,低位字节的寄存器分别称为 <code>AL,BL,CL,DL</code>,高位字节的寄存器分别称为 <code>AH,BH,CH,DH</code></li></ul></li><li>变址寄存器<ul><li><code>SI</code> 称为源变址寄存器(Source Index)</li><li><code>DI</code> 是目的变址寄存器(Destination Index)</li><li>常用于存储器变址寻址方式提供地址。在串操作类指令中,用于存放串首或串尾数据单元的偏移地址</li></ul></li><li>指针寄存器<ul><li><code>SP</code> 为堆栈指针寄存器(Stack Pointer),指示堆栈段栈顶的位置(偏移地址)</li><li><code>BP</code> 为基址指针寄存器(Base Pointer)</li><li><code>SP</code> 和 <code>BP</code> 寄存器与 <code>SS</code> 段寄存器联合使用以确定堆栈段中的存储单元地址</li></ul></li></ul><h4 id="段寄存器">段寄存器</h4><p>CPU 内部有 4 个段寄存器。8086/8088 CPU 对寻址的 <span class="math inline">\(1MB\)</span> 内存区域是分段管理的,定义的代码段用于存放指令代码,数据段和附加数据段用于存放数据,堆栈段是按照先进后出的访问原则组织起来的一段内存区域。这 4 个段寄存器为存储器分段管理技术提供了硬件支持</p><ul><li><code>CS</code> 称为代码段寄存器,用于存放 <strong>代码段</strong> 的 <strong>段基址</strong></li><li><code>DS</code> 称为数据段寄存器;<code>ES</code> 称为附加段寄存器,两者都用于存放 <strong>数据段</strong> 和 <strong>附加数据段</strong> 的 <strong>段基址</strong></li><li><code>SS</code> 称为堆栈段寄存器,用于存放 <strong>堆栈段</strong> 的 <strong>段基址</strong></li></ul><h4 id="指令指针寄存器">指令指针寄存器</h4><p><code>IP</code>(Instruction Pointer)为指令指针寄存器,指示内存中指令的位置。随着指令的执行,IP 将自动修改以指示下一条指令所在位置。<code>IP</code> 是一个 <strong>专用寄存器</strong>,与 <code>CS</code> 联合使用以确定下一条指令的存储单元地址</p><h4 id="标志寄存器">标志寄存器</h4><p>执行单元 <code>EU</code> 中有一个标志寄存器 <code>FLAGS</code>,16 位的 <code>FLAGS</code> 可分为标志位和控制位,<strong>标志位</strong> 指明程序运行时微处理器的实时状态;<strong>控制位</strong> 由程序设计者设置,以控制 CPU 进行某种操作</p><h2 id="位微处理器内部结构-1">32 位微处理器内部结构</h2><blockquote><p>Pentium 微处理器的内部寄存器长度都为 <span class="math inline">\(32\)</span> 位,但外部数据总线不像 80386 和 <strong>80486</strong> 那样是 <span class="math inline">\(32\)</span> 位,而是 <span class="math inline">\(64\)</span> 位,总线传输速度高达 <span class="math inline">\(66MHz\)</span>。同时它具有 <span class="math inline">\(32\)</span> 位地址总线,可直接寻址 <span class="math inline">\(4GB\)</span> 的物理内存空间。它有两条相对独立的指令并行流水线,即 U 流水线和 V 流水线</p></blockquote><h3 id="内部结构-1">内部结构</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220906094633.png" alt="image-20220906094633289" style="zoom:80%;" / loading="lazy"></p><ul><li><strong>总线接口单元</strong> 实现 CPU 与系统总线的连接,其中包括 64 位数据线、32 位地址线和众多控制信号线,以此实现相互之间的信息交换,并产生相应的总线周期信号</li><li><strong>分段分页单元</strong> 完成将各种地址映射到内存物理地址的功能</li><li><strong>高速缓存</strong> 即 Cache,是容量较小、速度很高的可读写 RAM,用来存放 CPU 最近要使用的数据和指令,Cache 可以加快 CPU 存取数据的速度,减轻总线负担。在 Pentium 微处理器内部,指令 Cache 和数据 Cache 是分开的,目的是提高访问的 <strong>命中率</strong></li><li><strong>指令预取部件</strong> 每次可以取两条指令,如果是简单指令,并且后一条指令不依赖前一条指令的执行结果,那么,指令预取部件便将两条指令分别送到 U 流水线和 V 流水线独立执行</li><li><strong>指令 Cache、指令预取部件</strong> 将原始指令送到指令译码器,分支目标缓冲器则在遇到分支转移指令时用来预测转移是否发生</li><li><strong>浮点处理单元</strong> 主要用于浮点运算,内含专用的加法器、乘法器和除法器,加法器和乘法器均能在 3 个时钟周期内完成相应的运算,除法器则在每个时钟周期产生 2 位二进制商</li><li><strong>控制 ROM</strong> 中含有 Pentium 微处理器的微代码,控制部件直接控制流水线操作。</li></ul><h3 id="寄存器结构-1">寄存器结构</h3><p>80486 内部寄存器分为四类:基本结构寄存器、浮点寄存器、系统级寄存器、调试测试寄存器。应用程序只能访问基本结构寄存器和浮点寄存器。这里只介绍基本结构寄存器,<strong>除了标志寄存器外,其余寄存器的命名和使用方法都没有改变</strong>。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220906095449.png" alt="image-20220906095449778" style="zoom:80%;" / loading="lazy"></p><h4 id="通用寄存器-1">通用寄存器</h4><p>能进行 32 位运算的寄存器分别称为 <code>EAX</code>、<code>EBX</code>、<code>ECX</code>、<code>EDX</code>、<code>ESI</code>、<code>EDI</code>、<code>EBP</code> 和 <code>ESP</code>。其中 <code>EAX</code>、<code>EBX</code>、<code>ECX</code>、<code>EDX</code> 的 <strong>低 16 位</strong> 构成了 16 位微处理器中的通用寄存器(去掉 E),然后 <strong>低 16 位</strong> 又可以进一步拆成 <strong>两个 8 位</strong> 通用寄存器。</p><h4 id="段寄存器-1">段寄存器</h4><p><strong>6 个 16 位的段寄存器</strong> 用于指示代码和数据所用的地址空间。代码段寄存器 <code>CS</code>、堆栈段寄存器 <code>SS</code>、<code>DS</code>、<code>ES</code>、<code>FS</code> 和 <code>GS</code> 都称为数据段寄存器,除 <code>CS</code> 用于指示指令代码的地址空间外,其他段寄存器都用于指示 <strong>数据</strong> 的地址空间。当微处理器工作在 <strong>实地址模式</strong> 下,这些段寄存器提供的内容就是 <strong>16 位的段基址</strong></p><table><thead><tr class="header"><th>逻辑段</th><th>段基址存放</th><th>偏移地址存放</th><th>初始值</th></tr></thead><tbody><tr class="odd"><td>代码段</td><td>CS</td><td>IP</td><td>操作系统赋值</td></tr><tr class="even"><td>堆栈段</td><td>SS</td><td>SP</td><td>程序员或操作系统赋值</td></tr><tr class="odd"><td>数据段</td><td>DS</td><td>根据不同的寻址方式选择 BX、SI、DI</td><td>程序员赋值</td></tr><tr class="even"><td>附加段</td><td>ES/FS/GS</td><td>根据不同的寻址方式选择 BX、SI、DI</td><td>程序员赋值</td></tr></tbody></table><h4 id="指令指针寄存器-1">指令指针寄存器</h4><p><code>EIP</code> 中存放相对于代码段寄存器 <code>CS</code> 的基址的偏移量。<code>EIP</code> 的低 16 位可作为独立使用的寄存器,称为 <code>IP</code>, 它在实地址模式下,与 <code>CS</code> 组合后,形成 20 位的物理地址</p><h4 id="标志寄存器-1">标志寄存器</h4><p>标志寄存器是 32 位的寄存器,称为 <code>EFLAGS</code>。<code>EFLAGS</code> 中的位同样分为状态标志位和控制标志位两类:</p><ul><li>状态标志位指明程序运行时的微处理器的实时状态,这种状态会像某种先决条件一样影响后面的操作。有 SF、ZF、PF、CF、AF 和 OF</li><li>控制标志位由程序设计者设置,有 DF、 IF、 TF。每个控制标志都对某一种特定的功能起控制作用</li><li><code>EFLAGS</code> 的低位也可作为一个独立的标志寄存器 <code>FLAGS</code>(又称为程序状态字 <code>PSW</code>)来使用。</li></ul><h2 id="位微处理器工作模式">32 位微处理器工作模式</h2><blockquote><p>80x86 系列的 32 位微处理器(80386 及其后继处理器)支持 16 位和 32 位指令系统,32 位指令系统是在 16 位指令系统的基础上扩展而成的。32 位处理器有 3 种工作模式:实地址模式(Real Address Mode)、保护虚拟地址模式(Protected Virtual Address Mode)和虚拟 8086 模式,简称为实模式、保护模式和虚拟 86 模式。</p></blockquote><h3 id="位微处理器地址空间">32 位微处理器地址空间</h3><p>80X86 系列的 32 位微处理器有 3 个明确的存储地址空间,它们是 <strong>物理空间</strong>、<strong>虚拟空间</strong> 和 <strong>线性空间</strong>。</p><ul><li>物理空间是计算机中主存储器的实际空间,也称为主存空间,相应的地址称为 <strong>物理地址</strong> 或 <strong>主存地址</strong>。任一存储单元都具有唯一的一个物理地址。对主存的访问最终必须通过物理地址来实现。32 位微处理器有两个独立的物理空间:一个是 <strong>物理存储空间</strong>,另一个是 <strong>物理 I/O 空间</strong>。80X86 的物理 I/O 空间由<span class="math inline">\(2^{16}(64K)\)</span>个地址组成。它与存储地址不重叠<ul><li>8086:20 根地址线,寻址范围<span class="math inline">\(1M\)</span></li><li>80486:32 根地址线,寻址范围<span class="math inline">\(4GB\)</span></li></ul></li><li><strong>虚拟空间又称为逻辑空间</strong>,是应用程序员编写程序的空间,此空间对应的存储器称为虚拟存储器,该存储空间对应的地址称为虚拟地址或逻辑地址。该空间可比主存实际能提供的空间大很多,即使主存空间不够大,也能运行程序员编写的程序</li><li>32 位微处理器通过 <strong>分段部件</strong> 把虚拟空间变换为 32 位的 <strong>线性空间</strong>,如果分页部件未被选用(实模式),线性地址就是物理地址</li></ul><h3 id="位微处理器工作模式-1">32 位微处理器工作模式</h3><h4 id="实地址模式">实地址模式</h4><blockquote><p>在实模式下,32 位微处理器与它的前款处理器 16 位的 8086 兼容,所以为 8086、80286 编写的程序不需要做任何修改,就可以在 32 位微处理器的实模式下运行,且速度更快。除此之外,在实模式下,还能有效地使用 8086 所没有的寻址方式、32 位寄存器和大部分指令。在实模式下,32 位微处理器具有与 8086 同样的基本体系结构。</p></blockquote><p>实地址的特点:</p><ol type="1"><li>加电、复位之后,486 自动工作在实模式,系统在 DOS 管理下</li><li>在实模式下,微处理器的地址线仅低 20 根启动,所以 486 只能访问最低端的 <span class="math inline">\(1M\)</span> 内存(<span class="math inline">\(00000H\sim FFFFFH\)</span>)</li><li>存储管理部件对存储器只进行分段管理,<strong>没有分页功能(线性地址即为物理地址)</strong>,每一逻辑段的最大容量为 <span class="math inline">\(64K\)</span></li><li>该模式下,段寄存器中存放段基址</li></ol><p>实地址物理地址形成:star:</p><p>实地址模式下,物理地址的地址信息是 20 位的二进制代码,以 16 进制表示是 <span class="math inline">\(00000H\sim FFFFFH\)</span> 中的一个单元,CPU 访问存储器时,地址总线上送出的是 20 位物理地址。但是由于之前的 8086/8088 处理的数据总线宽度只有 16 位,无法传输 20 位的地址,所以在 <strong>编程(虚拟)空间</strong> 引入了 <strong>逻辑地址</strong>,即 <strong>段地址:偏移地址</strong> 的模式,即用 4 个 16 进制表示物理地址。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220906103752.png" alt="image-20220906103752489" style="zoom:67%;" / loading="lazy"></p><p>系统将内存分为若干个逻辑段(最大 <span class="math inline">\(64K\)</span>),在同一逻辑段中,各单元的 16 位段地址是相同的,偏移地址是该单元相对于段首的 16 位地址偏移量。系统默认时,段都起始于 16 字节的边界,即段起始物理地址为 <span class="math inline">\(XXXX0H\)</span></p><ul><li>计算方法:将段地址左移 4 位后与偏移地址相加</li></ul><blockquote><p>段寄存器 <code>CS</code> 内容为 <span class="math inline">\(1000H\)</span>,偏移地址在 <code>IP</code> 寄存器中,为 <span class="math inline">\(2345H\)</span></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220906104115.png" alt="image-20220906104115472" style="zoom: 50%;" / loading="lazy"></p></blockquote><ul><li>物理地址唯一,逻辑地址不唯一(取决于分段的方式)</li></ul><h3 id="保护虚拟地址模式">保护虚拟地址模式</h3><p>特点:</p><ol type="1"><li>486 支持多任务操作系统</li><li>486 可以访问 4G 物理存储空间</li><li>CPU 内部的存储管理部件对存储器采用分段和分页管理。可以将磁盘等存储设备有效映射到内存,使逻辑地址空间大大超过实际的物理地址空间,这样使主存储器容量似乎很大</li><li>既能进行 16 位运算,也能进行 32 位运算。</li></ol><p>保护机制:高级别的程序可以访问同级或低级的数据段,反之则不行</p><h3 id="虚拟-8086-模式">虚拟 8086 模式</h3><blockquote><p>32 位微处理器允许在实模式和保护模式下执行 8086 的应用程序。后者为系统设计人员提供了 32 位微处理器保护模式的全部功能,因而具有更大的灵活性。保护模式的功能之一是能够在保护和多任务的环境中直接执行实模式的 8086 软件,这个特性称为虚拟 8086 模式,又简称为虚拟 86 模式,这不是一种实际的处理器方式,而是一种准操作方式。虚拟 8086 模式具有保护方式下的任务属性。</p></blockquote><ol type="1"><li>可以执行 8086 的应用程序</li><li>段寄存器的用法和实模式一样,即段寄存器内容左移 <span class="math inline">\(4\)</span> 位加上偏移地址即为线性地址</li><li>存储器寻址空间为 <span class="math inline">\(1MB\)</span></li></ol>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>中秋节,真不戳</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220906105428.jpg" alt="100953677_p0_master1200" style="zoom:67%;" /></p></summary>
<category term="汇编语言程序设计" scheme="http://lapras.xyz/categories/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="汇编" scheme="http://lapras.xyz/tags/%E6%B1%87%E7%BC%96/"/>
</entry>
<entry>
<title>计算机组成原理笔记(九)</title>
<link href="http://lapras.xyz/2022/08/29/c4a21082.html"/>
<id>http://lapras.xyz/2022/08/29/c4a21082.html</id>
<published>2022-08-29T14:36:12.275Z</published>
<updated>2022-08-29T14:43:03.757Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>真不戳,住在郊区真不戳</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/asdasd.jpg" alt="100819348_p0_master1200" style="zoom: 50%;" / loading="lazy"></p><span id="more"></span><h2 id="微操作命令分析">微操作命令分析</h2><p>控制单元具有发出各种 <strong>微操作命令</strong>(即控制信号)序列的功能。而不同的指令对应不同的命令。进一步分析发现,完成不同指令的过程中,有些操作是相同或相似的,如取指令、取操作数地址(当间接寻址时)以及进入中断周期由中断隐指令完成的一系列操作。为更清晰起见,下面按指令周期的 4 个阶段进一步分析其对应的微操作命令。</p><p>下面开始分析,其中除了执行周期以外的周期都在 <strong>指令流水</strong> 章中介绍过了,不再赘述</p><h3 id="执行周期">执行周期</h3><p>不同指令执行周期的微操作是不同的,下面分别讨论非访存指令、访存指令和转移类指令的微操作</p><h4 id="非访存指令">非访存指令</h4><p>顾名思义,这类指令在执行周期不会访问存储器</p><ol type="1"><li><p>清除累加器指令 <span class="math inline">\(CLA\)</span></p><p>完成清除累加器操作,记作 <span class="math inline">\(0 \to ACC\)</span></p></li><li><p>累加器取反指令 <span class="math inline">\(COM\)</span></p><p>将累加器中的内容取反,记作 <span class="math inline">\(\overline{ACC} \to ACC\)</span></p></li><li><p>算术右移一位指令 <span class="math inline">\(SHR\)</span></p><p>将累加器中的内容进行算术右移一位,先右移,记作 <span class="math inline">\(L(ACC)\to R(ACC)\)</span>,然后保证符号位(最高位)不变 <span class="math inline">\(ACC_0 \to ACC_0\)</span></p></li><li><p>循环左移一位指令 <span class="math inline">\(CSL\)</span></p><p>将累加器中的内容进行循环左移一位,先左移,记作 <span class="math inline">\(R(ACC) \to L(ACC)\)</span>,然后将原本的最高位变为最低位,记作 <span class="math inline">\(ACC_0 \to ACC_n\)</span></p></li><li><p>停机指令 <span class="math inline">\(STP\)</span></p><p>计算机中有一个运行标志触发器 <span class="math inline">\(G\)</span>,当其值为 1 时,表示机器运行;反之,则为停机。<span class="math inline">\(STP\)</span> 指令在执行阶段只需将运行标志触发器置零,记作 <span class="math inline">\(0 \to G\)</span></p></li></ol><h4 id="访存指令">访存指令</h4><p>这类指令在执行阶段都需要访问存储器。简单起见,这里只考虑直接寻址的情况</p><ol type="1"><li><p>加法指令 <span class="math inline">\(ADD \ X\)</span></p><p>该指令将了累加器的内容与主存 <span class="math inline">\(X\)</span> 地址单元的内容相加,结果送入累加器。具体过程如下</p><ol type="1"><li>将指令的地址码部分送至存储器地址寄存器,记作 <span class="math inline">\(Ad(IR)\to MAR\)</span></li><li>向主存发读命令,启动主存作读操作,记作 <span class="math inline">\(1 \to R\)</span>。</li><li>将 <span class="math inline">\(MAR\)</span>(通过地址总线)所指的主存单元中的内容(操作数)经数据总线读至 <span class="math inline">\(MDR\)</span> 内,记作 <span class="math inline">\(M(MAR)\to MDR\)</span>。</li><li>给 <span class="math inline">\(ALU\)</span> 发送加命令,将 <span class="math inline">\(ACC\)</span> 的内容和 <span class="math inline">\(MDR\)</span> 的内容相加,结果存于 <span class="math inline">\(ACC\)</span>, 记作 <span class="math inline">\((ACC)+(MDR)\to ACC\)</span></li></ol><p>当然,也有的加法指令指定两个寄存器的内容相加,如 <span class="math inline">\(ADD AX BX\)</span>,该指令在执行阶段无须访存,只需完成 <span class="math inline">\((AX)+(BX)\to AX\)</span> 的操作。</p></li><li><p>存数指令 <span class="math inline">\(STA \ X\)</span></p><p>该指令在执行阶段需将累加器 <span class="math inline">\(ACC\)</span> 的内容存于主存的 <span class="math inline">\(X\)</span> 地址单元中,具体操作如下</p><ol type="1"><li>将指令的地址码部分送至存储器地址寄存器,记作 <span class="math inline">\(Ad(IR) \to MAR\)</span></li><li>向主存发写命令,启动主存作写操作,记作 <span class="math inline">\(1\to W\)</span></li><li>将累加器内容送至 <span class="math inline">\(MDR\)</span>, 记作 <span class="math inline">\(ACC\to MDR\)</span></li><li>将 <span class="math inline">\(MDR\)</span> 的内容(通过数据总线)写入到 <span class="math inline">\(MAR\)</span>(通过地址总线)所指的主存单元中,记作 <span class="math inline">\(MDR\to M(MAR)\)</span></li></ol></li><li><p>取数指令 <span class="math inline">\(LDA\ X\)</span></p><p>该指令在执行阶段需将主存的 <span class="math inline">\(X\)</span> 地址单元中的内容取至累加器 <span class="math inline">\(ACC\)</span> 中,具体操作如下</p><ol type="1"><li>将指令的地址码部分送至存储器地址寄存器,记作 <span class="math inline">\(Ad(IR) \to MAR\)</span></li><li>向主存发读命令,启动主存作写操作,记作 <span class="math inline">\(1\to R\)</span></li><li>将 <span class="math inline">\(MAR\)</span>(通过地址总线)所指的主存单元中的内容(操作数)经过数据总线送入 <span class="math inline">\(MDR\)</span> 内,记作 <span class="math inline">\((MAR) \to MDR\)</span></li><li>将 <span class="math inline">\(MDR\)</span> 的内容送至 <span class="math inline">\(ACC\)</span> 中,记作 <span class="math inline">\(MDR\to ACC\)</span></li></ol></li></ol><h4 id="转移类指令">转移类指令</h4><ol type="1"><li><p>无条件转移指令 <span class="math inline">\(JMP \ X\)</span></p><p>该指令在执行阶段完成将指令的地址码部分 X 送至 PC 的操作,记作 <span class="math inline">\(Ad(IR)\to PC\)</span></p></li><li><p>条件转移指令中的 <strong>负则转</strong> 指令 <span class="math inline">\(BAN \ X\)</span></p><p>该指令根据上一条指令运行的结果决定下一条指令的地址,若结果为负(累加器最高位为 1, 即 <span class="math inline">\(A_0 =1\)</span>),则指令的地址码送至 <span class="math inline">\(PC\)</span>,否则程序按原顺序执行</p><p>由于在取指阶段已完成了 <span class="math inline">\((PC)+1\to PC\)</span>,所以当累加器结果不为负时,就按取指阶段形成的 <span class="math inline">\(PC\)</span> 执行,记作 <span class="math inline">\(A_0 \cdot{Ad}(IR)+\bar{A_0}(PC) \to PC\)</span></p></li></ol><h4 id="三类指令的指令周期">三类指令的指令周期</h4><ul><li>非访存:取指,执行</li><li>直接访存:取指,执行</li><li>间接访存:取指,间址,执行</li><li>转移:取指,执行</li><li>间接转移:取指,间址,执行</li></ul><h2 id="控制单元的功能">控制单元的功能</h2><h3 id="控制单元的外特性">控制单元的外特性</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8E%A7%E5%88%B6%E5%8D%95%E5%85%83%E7%9A%84%E5%A4%96%E7%89%B9%E6%80%A7.png" alt="image-20220829185843527" style="zoom:80%;" / loading="lazy"></p><h4 id="输入信号">输入信号</h4><ol type="1"><li><p>时钟</p><p>为了使控制单元按一定的先后顺序、一定的节奏发出各个控制信号,控制单元必须受时钟控制,即每一个时钟脉冲使控制单元发送一个操作命令,或发送一组需要同时执行的操作命令</p></li><li><p>指令寄存器</p><p>现行指令的操作码决定了不同指令在执行周期所需完成的不同操作,故指令的操作码字段是控制单元的输入信号,它与时钟配合可产生不同的控制信号</p></li><li><p>标志</p><p>控制单元有时需依赖 CPU 当前所处的状态(如 ALU 操作的结果)产生控制信号,控制单元要根据上条指令的结果是否为负而产生不同的控制信号。因此“标志”也是控制单元的输入信号。</p></li><li><p>来自系统总线(控制总线)的控制信号</p><p>例如,INTR 中断请求,HRQ 总线请求</p></li></ol><h4 id="输出信号">输出信号</h4><ol type="1"><li><p>CPU 内的控制信号</p><p>主要用于 CPU 内的寄存器之间的传送和控制 ALU 实现不同的操作</p></li><li><p>送至系统总线(控制总线)的信号</p><p>例如,命令主存或 I/O 读写,中断响应等</p></li></ol><h3 id="控制信号举例">控制信号举例</h3><h4 id="单总线结构数据通路方式">单总线结构数据通路方式</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%8D%95%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84%E5%9B%BE.png" alt="image-20220829211519492" style="zoom:80%;" / loading="lazy"></p><ul><li>内部总线:指同一部件,如 CPU 内部连接各寄存器及运算部件之间的总线</li><li>系统总线:指同一台计算机系统的各部件,如 CPU、内存、通道和各类 I/O 接口间互相连接的总线</li></ul><ol type="1"><li><p>寄存器之间的数据传送</p><p>例,<span class="math inline">\(PC\)</span> 内容送至 <span class="math inline">\(MAR\)</span>:</p><ol type="1"><li><span class="math inline">\((PC) \to Bus\)</span>,<span class="math inline">\(PCount\)</span> 有效</li><li><span class="math inline">\(Bus \to MAR\)</span>,<span class="math inline">\(MARin\)</span> 有效</li></ol></li><li><p>主存与 CPU 之间的数据传送</p></li></ol><p>例,CPU 从主存读取指令:</p><ol type="1"><li><p><span class="math inline">\((PC) \to Bus \to MAR\)</span>,<span class="math inline">\(PCout,MARin\)</span> 有效</p></li><li><p><span class="math inline">\(1 \to R\)</span>,<span class="math inline">\(CU\)</span> 经过控制总线发送读信号</p></li><li><p>$M(MAR) MDR <span class="math inline">\(,\)</span>MDRinE$(带有 <span class="math inline">\(E\)</span> 表示与主存的数据通路)有效</p></li><li><p><span class="math inline">\(MDR \to Bus \to IR\)</span>,<span class="math inline">\(MDRin, IRin\)</span> 有效</p></li><li><p><span class="math inline">\((PC)+1 \to PC\)</span></p></li><li><p>执行算术或逻辑运算</p><p>例,加法指令(其中一个操作数已在 <span class="math inline">\(ACC\)</span> 中)</p><ol type="1"><li><span class="math inline">\(Ad(IR) \to Bus \to MAR\)</span>,<span class="math inline">\(IRout,MARin\)</span> 有效</li><li><span class="math inline">\(1 \to R\)</span>,<span class="math inline">\(CU\)</span> 发送读命令</li><li><span class="math inline">\(M(MAR) \to MDR\)</span>,<span class="math inline">\(MDRinE\)</span> 有效</li><li><span class="math inline">\(MDR \to Bus \to Y\)</span>,<span class="math inline">\(MDRout, Yin\)</span> 有效,让操作数存放于暂存寄存器(因为 CPU 的数据总线是 <strong>单总线</strong> 只能同时传送一个输入信号,所以让 <span class="math inline">\(ACC\)</span> 走总线,而把另一个操作数放置于与 <span class="math inline">\(ACC\)</span> 有专用数据通路的 <span class="math inline">\(Y\)</span> 寄存器之中)</li><li><span class="math inline">\((ACC) +(Y) \to Z\)</span>,<span class="math inline">\(ACCout,ALUin\)</span> 有效,<span class="math inline">\(CU\)</span> 向 <span class="math inline">\(ALU\)</span> 发送加命令</li><li><span class="math inline">\(Z \to ACC\)</span>,<span class="math inline">\(Zout,ACCin\)</span> 有效</li></ol></li></ol><h4 id="专用数据通路方式">专用数据通路方式</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%93%E7%94%A8%E6%95%B0%E6%8D%AE%E9%80%9A%E8%B7%AF%E6%96%B9%E5%BC%8F.png" alt="image-20220829194527188" style="zoom:80%;" / loading="lazy"></p><ol type="1"><li>取指周期<ol type="1"><li><span class="math inline">\((PC) \to MAR\)</span>,<span class="math inline">\(C_0\)</span> 有效</li><li><span class="math inline">\((MAR) \to MEM\)</span>,<span class="math inline">\(C_1\)</span> 有效</li><li><span class="math inline">\(1 \to R\)</span>,<span class="math inline">\(CU\)</span> 向主存发送读命令</li><li><span class="math inline">\(M(MAR)\to MDR\)</span>,<span class="math inline">\(C_2\)</span> 有效</li><li><span class="math inline">\((MDR)\to IR\)</span>,<span class="math inline">\(C_3\)</span> 有效</li><li><span class="math inline">\((PC)+1 \to PC\)</span></li><li><span class="math inline">\(OP(IR) \to CU\)</span>,<span class="math inline">\(C_4\)</span> 有效,对指令译码</li></ol></li><li>间址周期<ol type="1"><li><span class="math inline">\((MDR)\to MAR\)</span>,<span class="math inline">\(C_5\)</span> 有效</li><li><span class="math inline">\((MAR) \to MEM\)</span>,<span class="math inline">\(C_1\)</span> 有效</li><li><span class="math inline">\(1 \to R\)</span>,<span class="math inline">\(CU\)</span> 向主存发送读命令</li><li><span class="math inline">\(M(MAR)\to MDR\)</span>,<span class="math inline">\(C_2\)</span> 有效,此时 <span class="math inline">\(MDR\)</span> 就保存了操作数的地址</li><li><span class="math inline">\((MDR)\to IR\)</span>,<span class="math inline">\(C_3\)</span> 有效</li></ol></li><li>执行周期,以 $ADD $ 为例<ol type="1"><li><span class="math inline">\((MDR)\to MAR\)</span>,<span class="math inline">\(C_5\)</span> 有效</li><li><span class="math inline">\((MAR) \to MEM\)</span>,<span class="math inline">\(C_1\)</span> 有效</li><li><span class="math inline">\(1 \to R\)</span>,<span class="math inline">\(CU\)</span> 向主存发送读命令</li><li><span class="math inline">\(M(MAR)\to MDR\)</span>,<span class="math inline">\(C_2\)</span> 有效,此时 <span class="math inline">\(MDR\)</span> 就保存了操作数本身</li><li><span class="math inline">\((MDR) + (ACC) \to ACC\)</span>,<span class="math inline">\(C_6,C_7,C_8\)</span> 有效</li></ol></li></ol><h3 id="多级时序系统">多级时序系统</h3><h4 id="机器周期">机器周期</h4><p>机器周期可看做是所有指令执行过程中的一个 <strong>基准时间</strong>,机器周期取决于指令的功能及器件的速度。分析发现,机器内的各种操作大致可归属为对 <strong>CPU 内部的操作</strong> 和 <strong>对主存的操作</strong> 两大类,由于 CPU 内部的操作速度较快,CPU 访存的操作时间较长,因此通常 <strong>以访问一次存储器的时间</strong> 定为基准时间较为合理,这个基准时间就是机器周期。又由于不论执行什么指令,都需要访问存储器取出指令,因此在 <strong>存储字长等于指令字长的前提下,取指周期也可看做机器周期</strong>。</p><h4 id="时钟周期">时钟周期</h4><p>在 <strong>一个机器周期里可完成若干个微操作</strong>,每个微操作都需要一定的时间,可用时钟信号来控制产生每一个微操作命令。时钟就好比计算机的心脏,只要接通电源,计算机内就会产生时钟信号。时钟信号可由机器主振电路(如晶体振荡器)发出的脉冲信号经整形(或倍频、分频)后产生,时钟信号的频率即为 <strong>CPU 主频</strong>。用时钟信号控制节拍发生器,就可产生 <strong>节拍</strong>。每个节拍的宽度正好对应一个 <strong>时钟周期</strong>。在每个节拍内机器可完成一个或几个需同时执行的操作,它是 <strong>控制计算机操作的最小时间单位</strong>。</p><h4 id="多级时序系统-1">多级时序系统</h4><ul><li>机器周期、节拍(状态)组成多级时序系统</li><li>一个指令周期包含若干个机器周期</li><li>一个机器周期包含若干个时钟周期</li></ul><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%A4%9A%E7%BA%A7%E6%97%B6%E5%BA%8F%E7%B3%BB%E7%BB%9F.png" alt="image-20220829215457197" style="zoom: 67%;" / loading="lazy"></p><p>一般来说,CPU 的主频越快,机器的运行速度也越快。在机器周期所含时钟周期数相同的前提下,两机平均指令执行速度之比等于两机主频之比。例如,CPU 的主频为 <span class="math inline">\(8MHz\)</span>,其平均指令执行速度为 <span class="math inline">\(0.8MIPS\)</span>。若想得到平均指令执行速度为 <span class="math inline">\(0.4MIPS\)</span> 的机器,则只需要用主频为 <span class="math inline">\((8MHz×0.4MIPS)/0.8MIPS=4MHz\)</span> 的 CPU 即可。实际上机器的速度不仅与主频有关,还与机器周期中所含的时钟周期数以及指令周期中所含的机器周期数有关。同样主频的机器,由于机器周期所含时钟周期数不同,运行速度也不同。机器周期所含时钟周期数少的机器,速度更快。</p><h3 id="控制方式">控制方式</h3><p>控制单元控制一条指令执行的过程实质上是依次执行一个确定的微操作序列的过程。由于不同指令所对应的微操作数及其复杂程度不同,因此每条指令和每个微操作所需的执行时间也不同。通常将如何形成控制不同微操作序列所采用的时序控制方式称为 <span class="math inline">\(CU\)</span> 的控制方式。常见的控制方式有同步控制、异步控制、联合控制和人工控制。</p><h4 id="同步控制">同步控制</h4><p>同步控制方式是指,任何一条指令或指令中任何一个微操作的执行都是事先确定的,并且都是受 <strong>统一基准时标的时序信号</strong> 所控制的方式。如果机器内的存储器存取周期不统一,那么只有把 <strong>最长的存取周期作为机器周期</strong>,才能采用同步控制,否则取指令和取数时间不同,无法用统一的基准。又如有些不访存的指令,执行周期的微操作较少,无须那么多节拍。因此,为了提高 CPU 的效率,在同步控制中又有三种方案</p><ol type="1"><li><p>采用定长的机器周期</p><p>一律以最长的微操作序列和最繁的微操作作为标准,采取完全统一的、具有相同时间隔和相同数目的节拍作为机器周期来运行各种不同的指令。显然,这种方案对于微操作序列较短的指令来说,会造成时间上的浪费。</p></li><li><p>采用不定长的机器周期</p><p>每个机器周期内的节拍数可以不等。这种控制方式可解决微操作执行时间不统一的问题。通常把大多数微操作安排在一个较短的机器周期内完成,而对某些复杂的微操作,采用 <strong>延长机器周期</strong> 或 <strong>增加节拍</strong> 的办法来解决。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%8D%E5%AE%9A%E9%95%BF%E6%9C%BA%E5%99%A8%E5%91%A8%E6%9C%9F.png" alt="image-20220829220707428" style="zoom:80%;" / loading="lazy"></p></li><li><p>采用中央控制和局部控制相结合的方法</p><p>这种方案将机器的大部分指令安排在统一的、较短的机器周期内完成,称为中央控制,而将少数操作复杂的指令中的某些操作(如乘除法和浮点运算等)采用局部控制方式来完成</p><p>在设计局部控制线路时需要注意两点:</p><ol type="1"><li>使局部控制的每一个节拍 <span class="math inline">\(T^*\)</span> 的宽度与中央控制的节拍宽度相同</li><li>将局部控制节拍作为中央控制中机器节拍的延续,插入到中央控制的执行周期内,使机器以同样的节奏工作,保证了局部控制和中央控制的同步。<span class="math inline">\(T^*\)</span> 的多少可根据情况而定</li></ol></li></ol><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%AD%E5%A4%AE%E6%8E%A7%E5%88%B6%E5%92%8C%E5%B1%80%E9%83%A8%E6%8E%A7%E5%88%B6%E7%9B%B8%E6%9C%BA%E5%92%8C.png" alt="image-20220829222552838" style="zoom: 60%;" / loading="lazy"></p><h4 id="异步控制">异步控制</h4><p>异步控制方式不存在基准时标信号,没有固定的周期节拍和严格的时钟同步,执行每条指令和每个操作需要多少时间就占用多少时间。这种方式微操作的时序由 <strong>专门的应答线路控制</strong>,即当 <span class="math inline">\(CU\)</span> 发出执行某一微操作的控制信号后,等待执行部件完成了该操作后发回 <strong>回答信号</strong>,再开始新的微操作,使 CPU 没有空闲状态,但因需要采用各种应答电路,故其结构比同步控制方式复杂。</p><h4 id="联合控制方式">联合控制方式</h4><p>同步控制和异步控制相结合就是联合控制方式。这种方式对各种不同指令的微操作实行大部分统一、小部分区别对待的办法。例如,对每条指令都有的取指令操作,采用同步方式控制;对那些时间难以确定的微操作,如 I/O 操作,则采用异步控制,以执行部件送回的“回答”信号作为本次微操作的结束。</p><h4 id="人工控制方式">人工控制方式</h4><p>人工控制是为了调机和软件开发的需要,在机器面板或内部设置一些开关或按键,来达到人工控制的目的。</p>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>真不戳,住在郊区真不戳</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/asdasd.jpg" alt="100819348_p0_master1200" style="zoom: 50%;" /></p></summary>
<category term="计算机组成原理" scheme="http://lapras.xyz/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="硬件" scheme="http://lapras.xyz/tags/%E7%A1%AC%E4%BB%B6/"/>
</entry>
<entry>
<title>计算机组成原理笔记(八)</title>
<link href="http://lapras.xyz/2022/08/28/be6f31dd.html"/>
<id>http://lapras.xyz/2022/08/28/be6f31dd.html</id>
<published>2022-08-28T08:37:25.238Z</published>
<updated>2022-08-28T08:38:25.323Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>开学了!G!</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/chao.jpg" alt="100750654_p0_master1200" style="zoom:80%;" / loading="lazy"></p><span id="more"></span><h2 id="cpu-结构">CPU 结构</h2><h3 id="控制器的功能">控制器的功能</h3><p>对于冯诺依曼结构的计算机而言,一旦程序进入存储器后,就可由计算机自动完成取指令和执行指令的任务,<strong>控制器</strong> 就是专用于完成此项工作的,它负责协调并控制计算机各部件执行程序的指令序列,其基本功能是取指令、分析指令和执行指令。</p><ol type="1"><li><p>取指令</p><p>控制器必须具备能自动地从存储器中取出指令的功能。为此,要求控制器能自动形成指令的地址,并能发出取指令的命令,将对应此地址的指令取到控制器中。第一条指令的地址可以人为指定,也可由系统设定。</p></li><li><p>分析指令</p><p>分析指令包括两部分内容:其一,分析此指令要完成什么操作,即控制器需发出什么操作命令;其二,分析参与这次操作的操作数地址,即操作数的有效地址。</p></li><li><p>执行指令 执行指令就是根据分析指令产生的“操作命令”和“操作数地址”的要求,形成操作控制信号序列。</p></li><li><p>控制程序的输入和运算结果的输出,即控制主机与 I/0 设备交换信息</p></li><li><p>总线管理</p></li><li><p>处理机器运行过程中出现的异常情况和特殊请求</p></li></ol><blockquote><p>总之,CPU 必须具有控制程序的顺序执行(称指令控制)、产生完成每条指令所需的控制命令(称操作控制)、对各种操作加以时间上的控制(称时间控制)、对数据进行算术运算和逻辑运算(数据加工)以及处理中断等功能。</p></blockquote><h3 id="cpu-结构框图">CPU 结构框图</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/CPU%E7%BB%93%E6%9E%84%E6%A1%86%E5%9B%BE.png" alt="image-20220822225230720" style="zoom:80%;" / loading="lazy"></p><ul><li>指令控制:<span class="math inline">\(PC\)</span>(指出了要取出的指令的地址)和 <span class="math inline">\(IR\)</span>(存放当前指令的地址)</li><li>操作控制 时间控制:<span class="math inline">\(CU\)</span>和时序电路</li><li>数据加工:<span class="math inline">\(ALU\)</span>和寄存器</li><li>处理中断:中断系统</li></ul><h3 id="cpu-的寄存器">CPU 的寄存器</h3><h4 id="用户可见寄存器">用户可见寄存器</h4><p>通常 CPU 执行机器语言访问的寄存器为用户可见寄存器,按其特征又可分为以下几类。</p><ol type="1"><li><p>通用寄存器:通用寄存器可由程序设计者指定许多功能,可用于存放操作数,也可作为满足某种寻址方式所需的寄存器。例如,基址寻址所需的基址寄存器、变址寻址所需的变址寄存器和堆栈寻址所需的栈指针,都可用通用寄存器代替。寄存器间接寻址时还可用通用寄存器存放有效地址的地址。</p></li><li><p>数据寄存器:数据寄存器用于存放操作数,其位数应满足多数数据类型的数值范围,有些机器允许使用两个连读的寄存器存放双倍字长的值。还有些机器的数据寄存器只能用于保存数据,不能用于操作数地址的计算。</p></li><li><p>地址寄存器:地址寄存器用于存放地址,其本身可以具有通用性,也可用于特殊的导址</p></li><li><p>条件码寄存器:这类寄存器中存放条件码,它们对用户来说是部分透明的。条件码是 CPU 根据运算结果由硬件设置的位,例如,算术运算会产生正、负、零或溢出等结果。条件码可被测试,作为分支运算的依据。</p></li></ol><h4 id="控制寄存器">控制寄存器</h4><p>CPU 中还有一类寄存器用于控制 CPU 的操作或运算。在一些机器里,大部分这类寄存器对用户是透明的。如以下四种寄存器在指令执行过程中起重要作用。</p><ol type="1"><li>MAR:存储器地址寄存器,用于存放将被访问的存储单元的地址</li><li>MDR:存储器数据寄存器,用于存放即将存入存储单元的数据,或者刚从存储单元中读出的数据</li><li>PC(不透明):程序计数器,用于存放现行指令的地址,通常具备计数功能。且能被某些转移类指令修改</li><li>IR:指令寄存器,存放当前执行的指令</li></ol><h4 id="状态寄存器">状态寄存器</h4><ol type="1"><li>状态寄存器:存放指令执行结果,或者软硬件状态</li><li>PSW 寄存器:存放程序码</li></ol><h2 id="指令周期">指令周期</h2><h3 id="基本性质">基本性质</h3><p>指令周期:取出并执行一条指令的全部时间。一般来说,可以将其分为 <strong>取指周期</strong> 和 <strong>执行周期</strong>。</p><h4 id="指令周期类型">指令周期类型</h4><p>显然的,每条指令的指令周期因为指令类型不同而不一样:</p><ol type="1"><li><p>指令周期只有取值周期:NOP,JMP</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F1.png" alt="image-20220823100920627" style="zoom:80%;" / loading="lazy"></p></li><li><p>指令周期有取值周期和执行周期,且两者长度基本相等:ADD</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F2.png" alt="image-20220823101051004" style="zoom: 80%;" / loading="lazy"></p></li><li><p>指令周期有取值周期和执行周期,且后者长于前者:MUL</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F3.png" alt="image-20220823101206461" style="zoom:80%;" / loading="lazy"></p></li><li><p>具有间接寻址的指令周期</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F4.png" alt="image-20220823101326600" style="zoom:80%;" / loading="lazy"></p></li><li><p>具有中断周期的指令周期</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F5.png" alt="image-20220823101413059" style="zoom:80%;" / loading="lazy"></p></li></ol><h4 id="指令周期流程">指令周期流程</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8C%87%E4%BB%A4%E5%91%A8%E6%9C%9F%E6%B5%81%E7%A8%8B.png" alt="image-20220823101935137" style="zoom:80%;" / loading="lazy"></p><h4 id="cpu-工作周期的标志">CPU 工作周期的标志</h4><p>总之,上述 4 个周期都有 CPU 访存操作,只是访存的目的不同。<strong>取指周期</strong> 是为了取指令,<strong>间址周期</strong> 是为了取有效地址,<strong>执行周期</strong> 是为了取操作数(当指令为访存指令时), <strong>中断周期</strong> 是为了保存程序断点。这 4 个周期又可称为 CPU 的工作周期,为了区别它们,在 CPU 内设置了 4 个 <strong>标志触发器</strong></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/CPU%E5%B7%A5%E4%BD%9C%E5%91%A8%E6%9C%9F%E7%9A%84%E6%A0%87%E8%AF%86.png" alt="image-20220823102943386" style="zoom:80%;" / loading="lazy"></p><p>FE、IND、EX 和 INT 分别对应取指、间址、执行和中断 4 个周期,并以“1”状态表示有效,它们分别由 1 → FE、1 → IND、1 → EX 和 1 → INT 这 4 个信号控制。</p><h3 id="数据流">数据流</h3><h4 id="取值周期的数据流">取值周期的数据流</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%8F%96%E5%80%BC%E5%91%A8%E6%9C%9F%E7%9A%84%E6%95%B0%E6%8D%AE%E6%B5%811.png" alt="image-20220823110159861" style="zoom:80%;" / loading="lazy"></p><ol type="1"><li>$(PC)MAR $:当前指令的内存地址送至存储器地址寄存器</li><li><span class="math inline">\(1\to R\)</span>:<span class="math inline">\(CU\)</span> 发出 <strong>读控制信号</strong>,经控制总线传送到主存</li><li><span class="math inline">\(M(MAR) \to MDR\)</span>:将 <span class="math inline">\(MAR\)</span> 所指的主存中的指令地址的内容,经过数据总线送入 <span class="math inline">\(MDR\)</span></li><li><span class="math inline">\(MDR \to IR\)</span>:将指令内容送入 <span class="math inline">\(IR\)</span></li><li>$(PC) +1 PC <span class="math inline">\(:\)</span>CU$ 发出控制信号,让 <span class="math inline">\(PC\)</span> 的值加一,形成下一条指令地址</li></ol><h4 id="间址周期的数据流">间址周期的数据流</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%AE%80%E7%9B%B4%E5%91%A8%E6%9C%9F%E7%9A%84%E6%95%B0%E6%8D%AE%E6%B5%81.png" alt="image-20220823112246582" style="zoom:80%;" / loading="lazy"></p><ol type="1"><li><span class="math inline">\(Ad(IR) \to MAR\)</span> 或者 <span class="math inline">\(Ad(MDR)\to MAR\)</span>:将指令的地址码送入 <span class="math inline">\(MAR\)</span></li><li><span class="math inline">\(1 \to R\)</span>:<span class="math inline">\(CU\)</span> 发出读控制信号</li><li><span class="math inline">\(M(MAR) \to MDR\)</span>:将 <span class="math inline">\(MAR\)</span> 所指的主存中的指令地址的内容,即有效地址 <span class="math inline">\(EA\)</span>,经过数据总线送入 <span class="math inline">\(MDR\)</span></li><li><span class="math inline">\(MDR \to Ad(IR)\)</span>:将 <span class="math inline">\(EA\)</span> 送到指令字的地址码字段</li></ol><h4 id="执行周期的数据流">执行周期的数据流</h4><p>由于不同的指令在执行周期的操作不同,因此执行周期的数据流是多种多样的,可能涉及 CPU 内部寄存器间的数据传送、对存储器(或 I/O)进行读写操作或对 ALU 的操作,因此,无法用统一的数据流图表示。</p><h4 id="中断周期的数据流">中断周期的数据流</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%AD%E6%96%AD%E5%91%A8%E6%9C%9F%E6%95%B0%E6%8D%AE%E6%B5%81.png" alt="image-20220823120003697" style="zoom:80%;" / loading="lazy"></p><p>这里用堆栈保存断点,用 <span class="math inline">\(SP\)</span> 表示栈顶地址</p><ol type="1"><li><span class="math inline">\((SP) -1 \to SP,(SP) \to MAR\)</span>:<span class="math inline">\(CU\)</span> 控制将 <span class="math inline">\(SP\)</span> 减一后的地址送入 <span class="math inline">\(MAR\)</span>,即本次断点要存储的目标地址</li><li><span class="math inline">\((MAR) \to M\)</span>:<span class="math inline">\(MAR\)</span> 会将地址经过地址总线送入存储器</li><li><span class="math inline">\(1 \to W\)</span>:<span class="math inline">\(CU\)</span> 发出控制信号,启动主存做写操作</li><li><span class="math inline">\((PC)\to MDR\)</span>:断点的内容(即恢复断点时要执行的下一条指令地址)由 <span class="math inline">\(PC\)</span> 传送给 <span class="math inline">\(MDR\)</span></li><li><span class="math inline">\((MDR) \to M\)</span>:<span class="math inline">\(MDR\)</span> 经数据总线,将内容写入 <span class="math inline">\(MAR\)</span> 送入存储器的地址中</li><li><span class="math inline">\(\text{向量地址}\to PC\)</span>:<span class="math inline">\(CU\)</span> 控制将中断服务程序的 <strong>入口地址</strong>(由向量地址形成部件产生)送入 <span class="math inline">\(PC\)</span>,开始执行中断程序的内容</li></ol><h2 id="指令流水">指令流水</h2><h3 id="指令流水原理">指令流水原理</h3><p>完成一条指令实际上也可分为许多阶段。为简单起见,把指令的处理过程分为取指令和执行指令两个阶段,在不采用流水技术的计算机里,取指令和执行指令是周而复始地重复出现,各条指令按顺序串行执行的。这种顺序执行虽然控制简单,但执行中各部件的利用率不高,如指令部件工作时,执行部件基本空闲,而执行部件工作时,指令部件基本空闲。如果指令执行阶段不访问主存,则完全可以利用这段时间取下一条指令,这样就使取下一条指令的操作和执行当前指令的操作同时进行,这就是指令流水的基本原理,其基本示意图如下</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%B5%81%E6%B0%B4%E7%BA%BF%E7%A4%BA%E6%84%8F%E5%9B%BE.png" alt="未命名绘图.drawio" style="zoom:80%;" / loading="lazy"></p><ul><li>图中展示的称为 <strong>二级流水(一次重叠)</strong>。由指令部件取出一条指令,并将它暂存起来,如果执行部件空闲,就将暂存的指令传给执行部件执行。与此同时,指令部件又可取出下一条指令并暂存起来,这称为 <strong>指令预取</strong>。显然,这种工作方式能加速指令的执行。如果取指和执行阶段在时间上完全重叠,相当于将指令周期减半。</li></ul><p>然而进一步分析流水线,就会发现存在两个原因使得上述理想情况不会发生</p><ol type="1"><li><strong>指令的执行时间一般大于取指时间</strong>,因此,取指阶段可能要等待一段时间,也即存放在指令部件缓冲区的指令还不能立即传给执行部件,缓冲区不能空出。</li><li>当遇到 <strong>条件转移指令</strong> 时,下一条指令是不可知的,因为必须等到执行阶段结束后,才能获知条件是否成立,从而决定下条指令的地址,造成时间损失。通常为了减少时间损失,采用 <strong>猜测法</strong>,即当条件转移指令从取指阶段进入执行阶段时,指令部件仍按顺序预取下一条指令。这样,如果条件不成立,转移没有发生,则没有时间损失;若条件成立,转移发生,则所取的指令必须丢掉,并再取新的指令。</li></ol><p>尽管这些因素降低了两级流水线的潜在效率,但还是可以获得一定程度的加速。为了进一步提高处理速度,可将指令的处理过程分解为更细的几个阶段,形成六级流水(五次重叠)</p><ol type="1"><li>取指(FI):从存储器取出一条指令并暂时存入指令部件的缓冲区</li><li>指令译码(DI):确定操作性质和操作数地址的形成方式</li><li>计算操作数地址(CO):计算操作数的有效地址,涉及寄存器间接寻址、间接寻址、变址、基址、相对寻址等各种地址计算方式</li><li>取操作数(FO):从存储器中取操作数(若操作数在寄存器中,则无须此阶段)</li><li>执行指令(EI):执行指令所需的操作,并将结果存于目的位置(寄存器中)</li><li>写操作数(WO):将结果存入存储器</li></ol><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%85%AD%E7%BA%A7%E6%B5%81%E6%B0%B4.png" alt="image-20220824160353262" style="zoom:80%;" / loading="lazy"></p><h3 id="影响指令流水性能的因素">影响指令流水性能的因素</h3><h4 id="结构相关">结构相关</h4><p>结构相关是当指令在重叠执行过程中,不同指令争用同一功能部件产生资源冲突时产生的,故又有资源相关之称。解决方法有三种:</p><ol type="1"><li>停顿(加入气泡):即让冲突的两条指令中的后一条,暂停一个时间周期再运行</li><li>设置两个独立的存储器分别存放操作数和指令,以免取指令和取操作数同时进行时互相冲突,使取某条指令和取另一条指令的操作数实现时间上的重叠。</li><li>指令预取技术,指令预取技术的实现基于访存周期很短的情况。例如,在执行指令阶段,取数时间很短,因此在执行指令时,主存会有空闲,此时,只要指令队列空出,就可取下一条指令,并放至空出的指令队列中,从而保证在执行第 K 条指令的同时对第 K+1 条指令进行译码,实现“执行 K”与“分析 K+1”的重叠。</li></ol><h4 id="数据相关">数据相关</h4><p>数据相关是流水线中的各条指令因重叠操作,可能改变对操作数的读写访问顺序,从而导致了数据相关冲突。根据指令间对同一寄存器读和写操作的先后次序关系,数据相关冲突可分为 <strong>写后读相关</strong>、<strong>读后写相关</strong> 和 <strong>写后写相关</strong>。解决办法有:</p><ol type="1"><li>后推法:采用后推法,即将相关指令延迟到所需操作数被写回到寄存器后再执行的方式</li><li>定向技术:又称为旁路技术或相关专用通路技术。其主要思想是不必待某条指令的执行结果送回到寄存器后,再从寄存器中取出该结果,作为下一条指令的源操作数,而是直接将执行结果送到其他指令所需要的地方</li></ol><h4 id="控制相关">控制相关</h4><p>控制相关主要是由转移指令引起的。统计表明,转移指令约占总指令的 1/4 左右,比起数据相关来,它会使流水线丧失更多的性能。执行转移指令时,根据是否发生转移,它可能将程序计数器 <span class="math inline">\(PC\)</span> 内容改变成转移目标地址,也可能只是使 <span class="math inline">\(PC\)</span> 加上一个增量,指向下一条指令的地址。这就意味着,必须等待转移指令完成时才能判断,否则可能进行无效的操作。</p><p>为了解决控制相关,可以采用尽早判别转移是否发生,尽早生成转移目标地址;预取转移成功或不成功两个控制流方向上的目标指令;加快和提前形成条件码;提高转移方向的猜准率等方法。</p><h3 id="流水线性能指标">流水线性能指标</h3><h4 id="吞吐率">吞吐率</h4><p>吞吐率是指在单位时间内流水线 <strong>所完成的指令的数量</strong>,或是 <strong>输出结果的数量</strong>,有 <strong>最大吞吐率</strong> 和 <strong>实际吞吐率</strong> 之分。流水线仅在连续流动(满负荷,无相关因素影响)时才可达到最大吞吐率。实际上由于流水线在开始时有一段建立时间(第一条指令输入后到其完成的时间),结束时有一段排空时间(最后一条指令输入后到其完成的时间),以及由于各种相关因素使流水线无法连续流动。因此,实际吞吐率总是小于最大吞吐率。</p><p>设任务数(指令数)为 <span class="math inline">\(n\)</span>,各指令分为 <span class="math inline">\(k\)</span> 段,<span class="math inline">\(k\)</span> 段流水线各段时间为 <span class="math inline">\(\Delta t\)</span>,花费的总时间为 <span class="math inline">\(T_k\)</span></p><p>则吞吐率 <span class="math inline">\(T_p = \frac{n}{T_k}\)</span></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%90%9E%E5%90%90%E7%8E%871.png" alt="image-20220824172449420" style="zoom:80%;" / loading="lazy"></p><p>观察上图,不难发现:第一条指令耗费的时间(装入时间)为 <span class="math inline">\(k\Delta t\)</span>,而后每经过 <span class="math inline">\(\Delta t\)</span>,都有一条指令完成</p><p>所以总时间 <span class="math inline">\(T_k = (k+n-1)\Delta t\)</span>,就可以得到</p><p>实际吞吐率 <span class="math inline">\(T_p = \frac{n}{ (k+n-1)\Delta t}\)</span></p><p>最大吞吐率 <span class="math inline">\(T_{pmax} = \lim_{n \to \infty} T_p = \frac{1}{\Delta t}\)</span></p><h4 id="加速比">加速比</h4><p>流水线的加速比是指 <span class="math inline">\(k\)</span> 段流水线的速度与等功能的非流水线的速度之比</p><p>设 <span class="math inline">\(T_0\)</span> 表示不使用流水线时的执行时间,即顺序执行所用的时间;<span class="math inline">\(T_k\)</span> 表示使用流水线时的执行时间。则计算流水线加速比的基本公式为 <span class="math inline">\(S_p = \frac{T_0}{T_k}\)</span> <span class="math display">\[S_p =\frac{n k \Delta t}{(k+n-1) \Delta t}=\frac{k n}{k+n-1}\]</span> 同样的,最大加速比 <span class="math inline">\(S_{pmax} = \lim_{n \to \infty} S_p = k\)</span>,即流水线的段数</p><h4 id="效率">效率</h4><p>指流水线的设备利用率,由于流水线有建立时间和排空时间,因此各功能段的设备不可能一直处于工作状态,总有一段空闲时间。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%B5%81%E6%B0%B4%E7%BA%BF%E6%95%88%E7%8E%87%E5%9B%BE.png" alt="image-20220824173826613" style="zoom:80%;" / loading="lazy"></p><p>在时空图上,流水线的效率定义为完成 <span class="math inline">\(n\)</span> 个任务占用的时空区有效面积与 <span class="math inline">\(n\)</span> 个任务所用的时间与 <span class="math inline">\(k\)</span> 个流水段所围成的时空区总面积之比,则效率 <span class="math inline">\(E = \frac{T_0}{k T_k }\)</span> <span class="math display">\[E =\frac{n k \Delta t}{k(k+n-1) \Delta t}=\frac{n}{k+n-1}\]</span></p><p>同样的,最大效率 <span class="math inline">\(E_{max} = \lim_{n \to \infty}E = 1\)</span>,即满效率</p><h3 id="流水线的多发技术">流水线的多发技术</h3><h4 id="超标量技术">超标量技术</h4><p>它是指在每个时钟周期内可同时并发多条独立指令,即以并行操作方式将两条或两条以上指令编译并执行。要实现超标量技术,要求处理机中配置多个功能部件和指令译码电路,以及多个寄存器端口和总线,以便能实现同时执行多个操作,此外还要编译程序决定哪几条相邻指令可并行执行。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E8%B6%85%E6%A0%87%E9%87%8F%E6%8A%80%E6%9C%AF.png" alt="image-20220824175701402" style="zoom:80%;" / loading="lazy"></p><h4 id="超流水线技术">超流水线技术</h4><p>将一些流水线寄存器插入到流水线段中,好比将流水线再分段。图中将原来的一个时钟周期又分成 3 段,使超流水线的处理器周期比普通流水线的处理器周期短,这样,在原来的时钟周期内,功能部件被使用 3 次,使流水线以 3 倍于原来时钟频率的速度运行。与超标量计算机一样,硬件不能调整指令的执行顺序,靠编译程序解决优化问题。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E8%B6%85%E6%B5%81%E6%B0%B4%E7%BA%BF%E6%8A%80%E6%9C%AF6.png" alt="image-20220824175826030" style="zoom:80%;" / loading="lazy"></p><h4 id="超长指令字技术">超长指令字技术</h4><p>超长指令字(VLIW)技术和超标量技术都是采用多条指令在多个处理部件中并行处理的体系结构,在一个时钟周期内能流出多条指令。但超标量的指令来自同一标准的指令流,VLIW 则是由编译程序在编译时挖掘出指令间潜在的并行性后,把多条能并行操作的指令组合成一条具有多个操作码字段的超长指令(指令字长可达几百位),由这条超长指令控制 VLIW 机中多个独立工作的功能部件,由每一个操作码字段控制一个功能部件,相当于同时执行多条指令。VLIW 较超标量具有更高的并行处理能力,但对优化编译器的要求更高,对 Cache 的容量要求更大。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E8%B6%85%E9%95%BF%E6%8C%87%E4%BB%A4%E5%AD%97%E6%8A%80%E6%9C%AF.png" alt="image-20220824175943831" style="zoom:80%;" / loading="lazy"></p><h2 id="中断系统">中断系统</h2><h3 id="概述">概述</h3><p>从前面分析可知,采用中断方式实现主机与 I/O 交换信息可使 CPU 和 I/O 并行工作,提高 CPU 的效率。其实,计算机在运行过程中,除了会遇到 I/O 中断外,还有许多意外事件发生,如电源突然掉电,机器硬件突然出现故障,人们在机器运行过程中想随机抽查计算的中间结果,实现人机联系等。此外,在实时处理系统中,必须及时处理某个事件或现象,例如,在过程控制系统中,当突然出现温度过高、电压过大等情况时,必须及时将这些信息送至计算机;由计算机暂时中断现行程序,转去执行中断服务程序,以解决这种异常情况。再如,计算机实现多道程序运行时, 可以通过分配给每道程序一个固定时间片,利用时钟定时发中断进行程序切换。在多处理机系统中,各处理器之间的信息交流和任务切换也可通过中断来实现。总之,为了提高计算机的效率,为了处理一些异常情况以及实时控制、多道程序和多处理机的需要,提出了中断的概念。</p><h4 id="引起中断的因素">引起中断的因素</h4><ol type="1"><li><p>人为设置的中断</p><p>这种中断一般称为 <strong>自愿中断</strong>,因为它是在程序中人为设置的,故一旦机器执行这种人为中断,便自愿停止现行程序而转入中断处理。比如 <strong>转管指令</strong>,是转至从 I/O 设备调入一批信息到主存的管理程序,也可能是转至将一批数据送往打印机打印的管理程序。显然,当用户程序执行了“转管指令”后,便中断现行程序,转入管理程序,这种转移完全是自愿的。</p></li><li><p><strong>转管指令</strong></p></li><li><p>程序性事故:溢出、操作码不能识别、除法非法</p></li><li><p>硬件故障:存储器故障、硬盘坏道、掉电</p></li><li><p>I/O 设备</p></li><li><p>外部事件:键盘中断键</p></li></ol><h3 id="中断请求标记和中断判优逻辑">中断请求标记和中断判优逻辑</h3><h4 id="中断请求标记">中断请求标记</h4><p>为了判断是哪个中断源提出请求,在中断系统中必须设置中断请求标记触发器,简称 <strong>中断请求触发器</strong>,记作 INTR。当其状态为“1”时,表示中断源有请求。这种触发器可集中设在 CPU 内, 组成一个中断请求标记寄存器。这些触发器既可以集中在 CPU 的中断系统内,也可以分散到各个中断源中(链式排队器)。</p><h4 id="中断判优逻辑">中断判优逻辑</h4><p>任何一个中断系统,在任一时刻,只能响应一个中断源的请求。但许多中断源提出请求都是随机的,当某一时刻有多个中断源提出中断请求时,中断系统必须按其优先顺序予以响应,这称为中断判优。其可以用 <strong>硬件实现</strong>,也可用 <strong>软件实现</strong></p><p>硬件实现</p><ol type="1"><li><p>分散在各个中断源中的接口电路 <strong>链式排队器</strong></p></li><li><p>集中在 CPU 中</p></li></ol><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/CPU%E4%B8%AD%E6%96%AD%E5%88%A4%E4%BC%98.png" alt="image-20220828143507273" style="zoom:80%;" / loading="lazy"></p><p>软件实现</p><ol type="1"><li><p>程序查询</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E8%BD%AF%E4%BB%B6%E5%AE%9E%E7%8E%B0%E4%B8%AD%E6%96%AD.png" alt="image-20220828143727207" style="zoom:67%;" / loading="lazy"></p></li></ol><h3 id="中断服务程序地址的寻找">中断服务程序地址的寻找</h3><ol type="1"><li><p>硬件向量法</p><p>硬件向量法就是利用硬件产生 <strong>向量地址</strong>,再由向量地址找到中断服务程序的入口地址。向量地址由 <strong>中断向量地址形成部件</strong> 产生,这个电路可分散设置在各个接口电路中), 也可设置在 CPU 内。由向量地址寻找中断服务程序的入口地址通常采用两种办法。一种在向量地址内存放一条 <strong>无条件转移指令</strong>,CPU 响应中断时,只要将向量地址送至 PC, 执行这条指令,便可无条件转向服务程序的入口地址。另一种是设置 <strong>向量地址表</strong>,该表设在存储器内,<strong>存储单元的地址为向量地址,存储单元的内容为入口地址</strong>,只要访问向量地址所指示的存储单元,便可获得入口地址。</p></li><li><p>软件查询法</p><p>用软件寻找中断服务程序入口地址的方法称为软件查询法。当查到某一中断源有中断请求时,接着安排一条 <strong>转移指令</strong>,直接指向此中断源的中断服务程序入口地址,机器便能自动进入中断处理。至于各中断源对应的人口地址,则由程序员(或系统)事先确定。这种方法不涉及硬设备,但查询时间较长。计算机可具备软、硬件两种方法寻找入口地址,使用户使用更方便、灵活。</p></li></ol><h3 id="中断响应">中断响应</h3><h4 id="响应中断的条件">响应中断的条件</h4><p>允许中断触发器 <span class="math inline">\(EINT =1\)</span></p><h4 id="响应中断的时间">响应中断的时间</h4><p>CPU 总是在指令执行周期结束后,响应任何中断源的请求。在指令执行周期结束后,CPU 向所有中断源发中断查询信号,CPU 获知哪个中断源有请求。若有中断,CPU 则进入中断周期;若无中断,则进入下一条指令的取指周期。在某些计算机中,有些指令执行时间很长,若 CPU 的查询信号一律安排在执行周期结束时刻,有可能因 CPU 发现中断请求过迟而出差错。为此,可在指令执行过程中设置若干个查询断点,CPU 在每个“查询断点”时刻均发中断查询信号,以便发现有中断请求便可及时响应。</p><h4 id="中断隐指令">中断隐指令</h4><p>CPU 中断后,进入中断周期。在该周期内,CPU 会完成一系列的工作</p><ol type="1"><li><p>保护程序断点</p><p>将当前程序计数器 <span class="math inline">\(PC\)</span> 的内容保存到存储器中的特定单元(0 号地址)内,也可以存入堆栈</p></li><li><p>寻找中断服务程序的入口地址</p><p>可以使用 <strong>硬件向量法</strong> 或者 <strong>程序查询法</strong> 得到入口地址,将入口地址送至 <span class="math inline">\(PC\)</span>。然后硬件向量法使用无条件转移指令,程序查询法 CPU 执行中断识别程序</p></li><li><p>关中断</p><p>CPU 进入中断周期,意味着 CPU 响应了某个中断源的请求,为了确保 CPU 响应后所需做的一系列操作不至于又受到新的中断请求的干扰,在中断周期内必须自动关中断,以禁止 CPU 再次响应新的中断请求。</p></li></ol><h3 id="中断屏蔽技术">中断屏蔽技术</h3><p>当 CPU 正在执行某个中断服务程序时,另一个中断源又提出了新的中断请求,而 CPU 又应了这个新的请求,暂时停止正在运行的服务程序,转去执行新的中断服务程序,这称为多重中断,又称中断嵌套,如果 CPU 对新的请求不予响应,待执行完当前的服务程后再响应,即为单重中断。中断系统若要具有处理多重中断的功能,必须具备各项条件。</p><h4 id="实现多重中断的条件">实现多重中断的条件</h4><ol type="1"><li>提前设置开中断指令</li><li>优先级高的中断源有权中断优先级别低的中断源</li></ol><h4 id="屏蔽技术">屏蔽技术</h4><ol type="1"><li>屏蔽触发器与屏蔽字</li><li>屏蔽技术可改变优先等级 严格地说,优先级包含响应优先级和处理优先级。响应优先级是指 CPU 响应各中断源请求的优先次序,这种次序往往是硬件线路已设置好的,不便于改动。处理优先级是指 CPU 实际对各中断源请求的处理优先次序。如果不采用屏蔽技术,响应的优先次序就是处理的优先次序。采用了屏蔽技术后,可以改变 CPU 处理各中断源的优先等级,从而改变 CPU 执行程序的轨迹。</li></ol>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>开学了!G!</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/chao.jpg" alt="100750654_p0_master1200" style="zoom:80%;" /></p></summary>
<category term="计算机组成原理" scheme="http://lapras.xyz/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="硬件" scheme="http://lapras.xyz/tags/%E7%A1%AC%E4%BB%B6/"/>
</entry>
<entry>
<title>计算机组成原理笔记(七)</title>
<link href="http://lapras.xyz/2022/08/21/4904199e.html"/>
<id>http://lapras.xyz/2022/08/21/4904199e.html</id>
<published>2022-08-21T07:24:16.820Z</published>
<updated>2022-08-21T07:24:34.273Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>本章主要介绍机器指令系统的分类、常见的寻址方式、指令格式以及设计指令系统时应考虑的各种因素。黑神话新实机演示真不错</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E9%BB%91%E8%9B%87%E9%98%BF%E6%96%AF%E9%A1%BF%E9%98%BF%E4%B8%89.jpeg" alt="20220820204111" style="zoom: 40%;" / loading="lazy"></p><span id="more"></span><h2 id="机器指令">机器指令</h2><p>指令是指示计算机执行某种操作的命令,<strong>是计算机运行的最小功能单位</strong>。一台计算机的所有指令的集合构成该机的指令系统,也称为 <strong>指令集</strong>。</p><h3 id="指令的一般格式">指令的一般格式</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8C%87%E4%BB%A4%E6%A0%BC%E5%BC%8F.png" alt="image-20220813150549742" style="zoom:80%;" / loading="lazy"></p><p>操作码:用来指明该指令所要完成的操作,如加法、减法、传送、移位、转移等。其位数往往反映了机器的操作种类,即机器允许的指令条数,如操作码占 7 位,则该机器最多包含 <span class="math inline">\(2^7 = 128\)</span> 条指令。</p><p>操作码的长度 <strong>可以是固定的,也可以是变化的</strong>。前者将 <strong>操作码集中放在指令字的一个字段内</strong>,这种格式便于硬件设计,指令译码时间短。对于操作码长度不固定的指令,其 <strong>操作码分散在指令字的不同字段</strong> 中。这种格式可有效地压缩操作码的平均长度。但会增加指令译码和分析的难度,使控制器的设计复杂。通常采用 <strong>扩展操作码技术</strong>,使操作码的长度随地址数的减少而增加,不同地址数的指令可以具有不同长度的操作码,从而在满足需要的前提下,有效地缩短指令字长。</p><h3 id="按地址码数目分类">按地址码数目分类</h3><h4 id="零地址指令">零地址指令</h4><p>顾名思义,只有操作码而无地址码的指令</p><ol type="1"><li>不需要操作数,如空操伤、停机、关中断等指令</li><li>堆栈计算机,两个操作数隐含存放在栈顶和次栈顶,计算结果压回栈顶(类似后缀表达式)</li></ol><h4 id="一地址指令">一地址指令</h4><ol type="1"><li><p>只需要单操作数,如加 1、减 1、取反、求补等</p><p>指令含义: <span class="math inline">\(OP(A_1)\to A_1\)</span> ,</p></li><li><p>需要两个操作数,但其中一个操作数隐含在某个寄存器(如隐含在 <strong>ACC</strong> )</p><p>指令含义: <span class="math inline">\((ACC)OP(A_1)\to ACC\)</span></p><p>注: <span class="math inline">\(A_1\)</span> 指某个主存地址, <span class="math inline">\((A_1)\)</span> 表示 <span class="math inline">\(A_1\)</span> 所指向的地址中的内容</p></li></ol><h4 id="二地址指令">二地址指令</h4><ol type="1"><li><p>需要两个操作数的算术运算、逻辑运算相关指令</p><p>指令含义: <span class="math inline">\((A_1)OP(A_2)\to A_1\)</span> 或 <span class="math inline">\((A_1)OP(A_2)\to A_2\)</span></p></li></ol><h4 id="三地址指令">三地址指令</h4><ol type="1"><li><p>需要三个操作数的算术运算、逻辑运算相关指令</p><p>指令含义: <span class="math inline">\((A_1)OP(A_2)\to A_3\)</span></p></li></ol><h4 id="四地址指令">四地址指令</h4><ol type="1"><li><p>需要三个操作数的算术运算、逻辑运算相关指令,且指定下一个要执行的指令</p><p>指令含义: <span class="math inline">\((A_1)OP(A_2)\to A_3\)</span> , <span class="math inline">\(A_4\)</span> 为下一条将要执行指令的地址 正常情况下:取指令之后程序计数器 <span class="math inline">\(PC+1\)</span> ,指向下一条指令 四地址指令:执行指令后,将 <strong>PC</strong> 的值修改为 <span class="math inline">\(A_4\)</span> 所指地址</p></li></ol><blockquote><p>地址码的位数决定了该指令的 <strong>直接寻址范围</strong>,比如 <span class="math inline">\(n\)</span> 位地址码的直接寻址范围为 <span class="math inline">\(2^n\)</span> 。</p><p>若指令总长度固定不变,则地址码数目越多,每个地址码的位数就越少,寻址能力越差</p></blockquote><p>假设给定指令字长为 32 位,操作码为定长操作码 8 位。则不同地址码数目区别如下:</p><table><thead><tr class="header"><th>地址码</th><th>访存次数</th><th>寻址范围</th></tr></thead><tbody><tr class="odd"><td>零地址</td><td>1</td><td>无地址码</td></tr><tr class="even"><td>一地址</td><td>3(结果存于 ACC 则为 2)</td><td><span class="math inline">\(2^{24}\)</span></td></tr><tr class="odd"><td>二地址</td><td>4(结果存于 ACC 则为 3)</td><td><span class="math inline">\(2^{12}\)</span></td></tr><tr class="even"><td>三地址</td><td>4</td><td><span class="math inline">\(2^8\)</span></td></tr><tr class="odd"><td>四地址</td><td>4</td><td><span class="math inline">\(2^6\)</span></td></tr></tbody></table><h3 id="按指令长度分类">按指令长度分类</h3><blockquote><p>指令字长:一条指令的总长度(可能会变)</p><p>机器字长:CPU 进行一次整数运算所能处理的二进制数据的位数(通常和 ALU 直接相关)</p><p>存储字长:一个存储单元中的二进制代码位数(通常和 MDR 位数相同)</p></blockquote><p>显然指令字长取决于:</p><ol type="1"><li>操作码的长度</li><li>操作数地址个数</li><li>操作数地址长度</li></ol><ul><li>定长指令字结构:指令系统中所有指令的长度都相等,即 <strong>指令字长等于存储字长</strong></li><li>变长指令字结构:指令系统中各种指令的长度不等,指令长度是机器字长的若干倍:半字长指令、单字长指令、双字长指令</li></ul><h3 id="按操作码长度分类">按操作码长度分类</h3><ul><li><p>定长操作码:指令系统中所有指令的操作码长度都相同</p><p>如果操作码固定为 <span class="math inline">\(n\)</span> 位,则该系统最多支持 <span class="math inline">\(2^n\)</span> 条指令</p></li><li><p>可变长操作码:指令系统中各指令的操作码长度可变</p><p>通过扩展操作码指令格式实现</p></li></ul><h3 id="扩展操作码技术">扩展操作码技术</h3><p><strong>定长指令字结构+可变长操作码</strong>。对于不同地址数量的指令使用长度不同的操作码</p><h4 id="保留码点法">保留码点法</h4><p>先假设指令字长为 16 位,每个地址码占 4 位。前 4 位为基础操作码 <span class="math inline">\(OP\)</span> ,令有 3 个四位长的地址字段 <span class="math inline">\(A_1\quad A_2 \quad A_3\)</span></p><p>若 4 位基本操作码若全部用于三地址指令,则有 16 条。但至少须将 1111 留作扩展操作码之用,即三地址指令为 15 条;同理,将 1111 1111 留作扩展操作码只用,即二地址指令为 15 条;同理,一地址指令和零地址指令也为 15 条</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%BF%9D%E7%95%99%E9%A9%AC%E5%BA%97.png" alt="image-20220813153608821" style="zoom:80%;" / loading="lazy"></p><p>在设计扩展操作码时,要注意以下两点:</p><ol type="1"><li>不允许短码是长码的前缀(对比哈夫曼编码),即不允许短操作码与长操作码的前面部分相同</li><li>各指令的操作码不能重复</li></ol><p>通常情况下,对使用频率较高的指令,分配较短的操作码;对使用频率较低的指令,分配较长的操作码,从而尽可能减少指令译码和分析的时间。</p><ul><li>不难发现,对于地址码长度为 <span class="math inline">\(n\)</span> 的指令来说,上一层留出 <span class="math inline">\(m\)</span> 种状态,下一层可以扩展出至多 <span class="math inline">\(m \times 2^n\)</span> 种状态</li></ul><h2 id="操作数类型和操作类型">操作数类型和操作类型</h2><h3 id="操作数类型">操作数类型</h3><p>机器中常见的操作数类型有地址、数字、字符、逻辑数据等</p><ol type="1"><li>地址:地址实际上也可看做是一种数据,在许多情况下要计算操作数的地址。如果是绝对地址,则可被认为 是一个无符号整数;如果是相对地址,则可被认为是一个有符号整数</li><li>数字:定点数,浮点数和十进制数</li><li>字符:ASCII,BCD</li><li>逻辑数:逻辑运算</li></ol><h3 id="数据在存储器中的存放方式">数据在存储器中的存放方式</h3><h4 id="存放次序">存放次序</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%A4%A7%E7%AB%AF%E5%B0%8F%E7%AB%AF.png" alt="image-20220816172411987" style="zoom:80%;" / loading="lazy"></p><p>这两种字节次序分别为:高字节为大端方式,低字节为小端方式</p><h4 id="存放方式">存放方式</h4><p>以下图所示的存储器存储字长为 64 位,可按字节、半字、字、双字访问。由于不同的机器数据字长不同,每台机器处理的数据字长也不统一,例如奔腾处理器可处理 8(字节)、16(字)、32(双字)、64(四字);PowerPC 可处理 8(字节)、16(半字)、32(字)、64(双字)。</p><ol type="1"><li><p>从任意位置开始</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%BB%BB%E6%84%8F%E4%BD%8D%E7%BD%AE%E5%AD%98%E6%94%BE.png" alt="image-20220816230817830" style="zoom:80%;" / loading="lazy"></p><p>优点:不浪费存储资源 缺点:除了访问一个字节之外,访问其它任何类型的数据,都可能花费两个存储周期的时间。读写控制比较复杂。</p></li><li><p>从一个存储字的起始位置开始</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%80%E4%B8%AA%E5%AD%98%E5%82%A8%E5%AD%97%E7%9A%84%E8%B5%B7%E5%A7%8B%E4%BD%8D%E7%BD%AE%E5%BC%80%E5%A7%8B8.png" alt="image-20220816230928672" style="zoom:80%;" / loading="lazy"></p><p>优点:无论访问何种类型的数据,在一个周期内均可完成,读写控制简单。 缺点:浪费了宝贵的存储资源</p></li><li><p><strong>边界对准方式</strong>:从地址的整数倍位置开始访问</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E8%BE%B9%E7%95%8C%E5%AF%B9%E5%87%86%E6%96%B9%E5%BC%8F.png" alt="image-20220816231046351" style="zoom:80%;" / loading="lazy"></p><p>在对准边界的 64 位字长的计算机中,半字地址是 2 的整数倍,字地址是 4 的整数倍,双字地址是 8 的整数倍。当所存数据不能满足此要求时,可填充一个至多个空白字节。</p><p>本方案是前两个方案的折衷,在一个周期内可以完成存储访问,空间浪费也不太严重。</p></li></ol><h3 id="操作类型">操作类型</h3><ol type="1"><li><p>数据传送</p><p>LOAD:把存储器中的数据放到寄存器中 STORE:把寄存器中的数据放到存储器中</p></li><li><p>算逻操作</p><p>算术:加、减、乘、除、增 1、减 1、求补、浮点运算、十进制运算 逻辑:与、或、非、异或、位操作、位测试、位清除、位求反</p></li><li><p>移位操作</p></li></ol><p>算术移位、逻辑移位、循环移位(带进位和不带进位)</p><ol start="4" type="1"><li><p>转移操作 无条件转移、条件转移、调用和返回 、陷阱与陷阱指令</p></li><li><p>输入输出操作</p><p>CPU 寄存器与 IO 端口支间的数据传送(端口即 IO 接口中的寄存器)</p></li></ol><h2 id="寻址方式">寻址方式</h2><p>寻址方式是指确定本条指令的数据地址以及下一条将要执行的指令地址的方法,它与硬件结构紧密相关,而且直接影响指令格式和指令功能。寻址方式分为指令寻址和数据寻址两大类。</p><h3 id="指令寻址">指令寻址</h3><p>顺序寻址:取完一条指令后,顺序地读取下一条指令。而指令的地址保存在 <span class="math inline">\(PC\)</span> 当中。</p><p><span class="math inline">\((PC)+1 \to PC\)</span></p><ul><li>这里的 1 并非是数字 1 ,而是根据编址方式调整的,表示一个指令字长。</li></ul><p>跳跃寻址:跳跃寻址通过转移类指令实现,比如 JMP</p><h3 id="数据寻址">数据寻址</h3><p>数据寻址方式种类较多,在指令字中必须设一字段来 <strong>指明属于哪一种寻址方式</strong>。指令的地址码字段通常都不代表操作数的真实地址,把它称为 <strong>形式地址</strong>,记作 A 。操作数的真实地址称为 <strong>有效地址</strong>,记作 EA , 它是由寻址方式和形式地址共同来确定的。由此可得指令的格式应如:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%95%B0%E6%8D%AE%E5%AF%BB%E5%9D%80%E6%A0%BC%E5%BC%8F.png" alt="image-20220818215214674" style="zoom:80%;" / loading="lazy"></p><p>以下寻址方式介绍,基于 <strong>指令字长 = 存储字长 = 机器字长</strong> 的假设前提</p><h3 id="立即寻址">立即寻址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%AB%8B%E5%8D%B3%E5%AF%BB%E5%9D%80.png" alt="image-20220818220734432" style="zoom:80%;" / loading="lazy"></p><ul><li>形式地址 A 存放的不是地址,而是操作数本身,被称为 <strong>立即数</strong>,采用 <strong>补码形式存放</strong></li><li><span class="math inline">\(\#\)</span> 是立即寻址的特征标记</li><li>优点:只要取出指令,便可立即获得操作数,这种指令在执行阶段 <strong>不必再访问存储器</strong></li><li>缺点:显然 A 的位数限制了这类指令所能表述的立即数的范围</li></ul><h3 id="直接寻址">直接寻址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%9B%B4%E6%8E%A5%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F.png" alt="image-20220818222404551" style="zoom:80%;" / loading="lazy"></p><ul><li>有效地址 EA = 形式地址 A,有效地址由形式地址给出</li><li>优点:寻找操作数比较简单,也不需要专门计算操作数的地址,在指令执行阶段 <strong>对主存只访问一次</strong></li><li>缺点:A 的位数限制了操作数的寻址范围,而且必须修改 A 的值,才能修改操作数的地址。</li></ul><h3 id="隐含寻址">隐含寻址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E9%9A%90%E5%90%AB%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F.png" alt="image-20220818223757476" style="zoom:80%;" / loading="lazy"></p><ul><li>指令字中不直接地给出操作数的地址,其操作数的地址隐含在操作码或某个寄存器中</li><li>优点:由于隐含寻址在指令字中少了一个地址。因此,这种寻址方式的指令有利于缩短指令字长</li></ul><h3 id="间接寻址">间接寻址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E9%97%B4%E6%8E%A5%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F.png" alt="image-20220818225251882" style="zoom:80%;" / loading="lazy"></p><ul><li>指令字中的形式地址不直接指出操作数的地址,而是指出操作数有效地址所在的存储单元地址</li><li>间接寻址,可以进行若干次。</li><li>优点:可扩大寻址范围(比如用存储字的首位表示是否要继续间址);便于编写程序</li><li>缺点:程序执行阶段进行最少两次(一次间址)的访存,运行较慢</li></ul><h3 id="寄存器直接寻址">寄存器(直接)寻址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%AF%84%E5%AD%98%E5%99%A8%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F.png" alt="image-20220818230609269" style="zoom:80%;" / loading="lazy"></p><ul><li>指令字中,地址码字段直接指出了寄存器的编号,即 <span class="math inline">\(EA = R_i\)</span></li><li>优点:操作数不在主存中,而在寄存器中。所以访存次数为 0;地址字段只需指明寄存器的编号(寄存器个数非常有限),故指令字长度较短。</li></ul><h3 id="寄存器间接寻址">寄存器间接寻址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%AF%84%E5%AD%98%E5%99%A8%E9%97%B4%E6%8E%A5%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F.png" alt="image-20220818231120511" style="zoom:80%;" / loading="lazy"></p><ul><li>有效地址保存在寄存器当中,操作数保存在存储器中。即 <span class="math inline">\(EA = (R_i)\)</span></li><li>优点:便于循环程序的编写</li><li>缺点:需要访问一次主存,但比间接寻址少访问一次</li></ul><h3 id="基址寻址">基址寻址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%9F%BA%E5%9D%80%E5%AF%BB%E5%9D%80.png" alt="image-20220818231546155" style="zoom:80%;" / loading="lazy"></p><ul><li><strong>隐式基址寻址</strong> 需设有基址寄存器 <span class="math inline">\(BR\)</span> ,使用时用户无需特意指出。</li></ul><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%9F%BA%E5%80%BC%E5%AF%BB%E5%9D%80%E9%99%90%E6%97%B6.png" alt="image-20220818232654260" style="zoom:80%;" / loading="lazy"></p><ul><li><p><strong>显式基址寻址</strong> 是在一组通用寄存器里,由用户明确指出哪个寄存器用做基址寄存器 <span class="math inline">\(R_0\)</span> ,存放基地址。</p></li><li><p>其操作数的有效地址 <span class="math inline">\(EA\)</span> 等于指令字中的形式地址与基址寄存器中的内容(称为基地址)相加,即 <span class="math inline">\(EA = (BR) +A\)</span></p></li><li><p>优点:扩大寻址范围;有利于多道程序设计,因为用户可不必考虑自己的程序存于主存的哪一空间区域,完全可由操作系统或管理程序根据主存的使用状况,赋予基址寄存器内一个初始值(即基地址), 便可将用户程序的逻辑地址转化为主存的物理地址(实际地址)。</p></li><li><p>注意:指令执行过程中,<strong>BR</strong> 的内容不允许修改,只能修改 <span class="math inline">\(A\)</span> 的内容</p></li></ul><h3 id="变址寻址">变址寻址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%8F%98%E5%9D%80%E5%AF%BB%E5%9D%80.png" alt="image-20220818232957427" style="zoom:80%;" / loading="lazy"></p><ul><li>变址寻址与基址寻址极为相似。 <span class="math inline">\(IX\)</span> 为变址寄存器(专用),通用寄存器也可以作为变址寄存器。其有效地址 <span class="math inline">\(EA = (IX)+A\)</span></li><li>优点:扩大寻址范围;有利于处理数组问题,可设定 <span class="math inline">\(A\)</span> 为数组的首地址,不断改变变址寄存器 <span class="math inline">\(IX\)</span> 的内 容,便可很容易形成数组中任一数据的地址,特别适合编制循环程序。</li><li>注意:指令执行过程中,<strong>A</strong> 的内容不允许修改,只能修改 <span class="math inline">\(IX\)</span> 的内容</li></ul><h3 id="相对寻址">相对寻址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%9B%B8%E5%AF%B9%E5%AF%BB%E5%9D%80.png" alt="image-20220819170029022" style="zoom:80%;" / loading="lazy"></p><ul><li>相对寻址的有效地址是将程序计数器 <span class="math inline">\(PC\)</span> 的内容(即当前指令的地址)与指令字中的形式地址 <span class="math inline">\(A\)</span> 相加而成,即 <span class="math inline">\(EA = (PC)+A\)</span></li><li><span class="math inline">\(A\)</span> 是相对于当前指令的位移量,可正可负,用补码表示。又称位移量</li><li>倘若位移量为 8 位,则指令的寻址范围在 <span class="math inline">\([(PC)-128, (PC)+127]\)</span> 之间。相对寻址的最大特点是转移地址不固定,它可随 <span class="math inline">\(PC\)</span> 值的变化而变,因此,无论程序在主存的哪段区域,都可正确运行,对于编写浮动程序特别有利。</li></ul><h3 id="堆栈寻址">堆栈寻址</h3><p>堆栈寻址要求计算机中设有 <strong>堆栈</strong>。堆栈既可用寄存器组(称为 <strong>硬堆栈</strong>)来实现,也可利用主存的一部分空间作堆栈(称为 <strong>软堆栈</strong>)。先进后出型堆栈的操作数只能从一个口进行读或写。以软堆栈为例,可用堆栈指针 SP(Stack Point)指出栈顶地址,也可用 CPU 中一个或两个寄存器作为 <span class="math inline">\(SP\)</span> 。操作数只能从栈顶地址指示的存储单元存或取。可见堆栈寻址也可视为一种隐含寻址,其操作数的地址总被隐含在 <span class="math inline">\(SP\)</span> 中。堆栈寻址其本质也可视为寄存器间接寻址,因 <span class="math inline">\(SP\)</span> 可视为寄存器,它存放着操作数的有效地址。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%A0%86%E6%A0%88%E5%AF%BB%E5%9D%80.png" alt="image-20220819171018635" style="zoom:80%;" / loading="lazy"></p><ul><li>同样的,这里的 1 要根据编址方式确定,若按照字编址,则为 1。若按照字节编址,则根据存储字长决定,比如 32 位为 4 个字节</li></ul><h2 id="cisc-和-risc">CISC 和 RISC</h2><h3 id="cisc">CISC</h3><p>Complex Instruction Set Computer</p><p>设计思路:一条指令完成一个复杂的操作</p><p>代表:x86 架构</p><h3 id="risc">RISC</h3><p>Reduced Instruction Set Computer</p><p>设计思路:一条指令完成一个基本操作,多条指令组合完成一个复杂操作</p><p>代表:ARM 架构</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/RISCandCise.png" alt="image-20220820202440113" style="zoom:80%;" / loading="lazy"></p>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>本章主要介绍机器指令系统的分类、常见的寻址方式、指令格式以及设计指令系统时应考虑的各种因素。黑神话新实机演示真不错</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E9%BB%91%E8%9B%87%E9%98%BF%E6%96%AF%E9%A1%BF%E9%98%BF%E4%B8%89.jpeg" alt="20220820204111" style="zoom: 40%;" /></p></summary>
<category term="计算机组成原理" scheme="http://lapras.xyz/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="硬件" scheme="http://lapras.xyz/tags/%E7%A1%AC%E4%BB%B6/"/>
</entry>
<entry>
<title>计算机组成原理笔记(六)</title>
<link href="http://lapras.xyz/2022/08/08/9b046e01.html"/>
<id>http://lapras.xyz/2022/08/08/9b046e01.html</id>
<published>2022-08-08T15:53:07.000Z</published>
<updated>2022-08-21T07:25:30.313Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>绝区零好潮。。。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220808231206.jpeg" alt="img" style="zoom:80%;" / loading="lazy"></p><span id="more"></span><h2 id="无符号数和有符号数">无符号数和有符号数</h2><h3 id="无符号数">无符号数</h3><p>计算机中的数均放在 <strong>寄存器</strong> 中,通常称寄存器的位数为 <strong>机器字长</strong>。<strong>所谓无符号数,即没有符号的数</strong>,在寄存器中的每一位均可用来存放数值。当存放有符号数时,则需留出位置存放符号。因此,在机器字长相同时,无符号数与有符号数所对应的数值范围是不同的。比如用一个八位的寄存器保存无符号数,则可以表示 0~255。</p><h3 id="有符号数">有符号数</h3><h4 id="机器数与真值">机器数与真值</h4><p>因为计算机没有办法直接识别正负号,所以需要将正负号转化为数字来存储。保存在计算机中将正负号“数字化”之后的数称为 <strong>机器数</strong>,而 <strong>真值</strong> 是我们日常生活中包含了正负号的数值。注意:以下内容默认都为 2 进制数</p><h4 id="原码表示法">原码表示法</h4><p>原码是机器数中最简单的一种表示形式,符号位为 0 表示正数,符号位为 1 表示负数,<strong>数值位即真值的绝对值</strong>,故原码表示又称为带符号的绝对值表示。为了书写方便以及区别整数和小数,约定整数的符号位与数值位之间用逗号隔开;小数的符号位与数值位之间用小数点隔开。由此可得原码的定义: <span class="math display">\[[x]_{\text {原}}=\left\{\begin{array}{cc}0, \ x & 2^{n}> x \geqslant 0 \\2^{n}-x & 0 \geqslant x > -2^{n}\end{array}\right.\]</span> 例如,当 <span class="math inline">\(x = -1110\)</span> 时, <span class="math inline">\([x]_{\text {原}} = 2^4 - (-1110) = 1,1110\)</span></p><p>对于小数部分: <span class="math display">\[[x]_{\text {原 }}=\left\{\begin{array}{cc}x & 1 > x \geqslant 0 \\1-x & 0 \geqslant x >-1\end{array}\right.\]</span> 例如,当 <span class="math inline">\(x = +0.1101\)</span> 时, <span class="math inline">\([x]_{\text {原}} = 0.1101\)</span> ,注意这里两个 <span class="math inline">\(0.\)</span> 的含义不同,后者表示正数+区分符</p><ul><li><p>不难发现,对于 <span class="math inline">\(0\)</span> 而言, <span class="math inline">\([+0000] _{\text {原}} = 0,0000 ;\ [-0000]_{\text {原}} = 1,0000\)</span> ,两者并不相同</p></li><li><p>原码特点:简单且直观,但运算复杂。当两个操作数符号不同且要作加法运算时,先要判断两数绝对值大小,然后将绝对值大的数减去绝对值小的数,结果的符号以绝对值大的数为准。而且用到的是减法器实现。</p></li></ul><h4 id="补码表示法">补码表示法</h4><p>为了解决原码在计算中的问题,即用一个与负数等价的正数表示负数,使得运算中只需要加法器。</p><p>所谓补数,就是其本身加上 <strong>模</strong>。正数的补数是其自身,负数的补数是其本身加上模,当一个正数和一个负数互为补数的时候,其绝对值之和为模数。那么对于 2 进制数而言,模就是 <span class="math inline">\(2^n\)</span> ,但是为了能表示正负,即负数的首位为 1,选择将模设置为 <span class="math inline">\(2^{n+1}\)</span> 。 <span class="math display">\[[x]_{\text {补 }}= \begin{cases}0,\ x & 2^{n}> x \geqslant 0 \\ 2^{n+1}+x & 0 > x \geqslant-2^{n} \quad\left(\bmod 2^{n+1}\right)\end{cases}\]</span> 例如, <span class="math inline">\(x = -1011000\)</span> ,则 <span class="math inline">\([x]_{\text{补}} = 2^{7+1} + (-1011000) = 1,0101000\)</span></p><p>小数: <span class="math display">\[[x]_{\text {补 }}= \begin{cases}x & 1 > x \geqslant 0 \\ 2+x & 0 > x \geqslant-1 \quad(\bmod 2)\end{cases}\]</span></p><p>例如, <span class="math inline">\(x = -0.1100000\)</span> ,则 <span class="math inline">\([x]_{\text{补}} = 2+(-0.1100000) = 1.0100000\)</span></p><ul><li>我们也可以通过口诀“<strong>原码取反(变为反码)再加 1</strong>”快速计算一个数的补码</li><li>不难发现,对于 <span class="math inline">\(0\)</span> 而言, <span class="math inline">\([+0000] _{\text {补}} = 0,0000 ;\ [-0000]_{\text {补}} = 2^{4+1} - 0000 = 100000 -0000 = 0,0000\)</span> ,最高位溢出舍去,所以补码的 0 表现形式是一致的</li></ul><h4 id="反码表示法">反码表示法</h4><p>反码通常用来作为由原码求补码或者由补码求原码的中间过渡。反码的定义如下: <span class="math display">\[[x]_{\text {反 }}=\left\{\begin{array}{ll}0,\ x & 2^{n}> x \geqslant 0 \\\left(2^{n+1}-1\right)+x & 0 \geqslant x >-2^{n}\left(\bmod 2^{n+1}-1\right)\end{array}\right.\]</span> 小数: <span class="math display">\[[x]_{\text {反 }}= \begin{cases}x & 1 > x \geqslant 0 \\ \left(2-2^{-n}\right)+x & 0 \geqslant x >-1 \quad\left(\bmod \left(2-2^{-n}\right)\right)\end{cases}\]</span> 例如, <span class="math inline">\(x = -0.1010\)</span> ,则 <span class="math display">\[\begin{align*}[x]_{\text{反}} &= (2 - 2 ^{-4}) + (-0.1010)) \\&= (10 - 0.0001) -0.1010 \\&= 1.1111 - 0.1010 \\&= 1.0101 \end{align*}\]</span></p><ul><li>简而言之为,符号位不变,正数不变,负数取反</li><li>不难发现,对于 <span class="math inline">\(0\)</span> 而言, <span class="math inline">\([+0000] _{\text {反}} = 0,0000 ;\ [-0000]_{\text {反}} = 2^{4+1}-1 - 0000 = 11111 - 0000 = 1,1111\)</span> ,两者并不一致。</li></ul><h4 id="小结">小结</h4><ol type="1"><li>最高位为符号位,书写上用“,”或“.”将数值部分与符号位分隔开</li><li>对于正数,原码 = 反码 = 补码</li><li>对于负数,符号位为 1。其数值部分原码取反得反码,反码加 1 得补码</li></ol><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%8E%9F%E5%8F%8D%E8%A1%A5%E5%B0%8F%E7%BB%93.png" alt="image-20220726222748948" style="zoom:80%;" / loading="lazy"></p><h4 id="移码表示法">移码表示法</h4><p>补码虽然计算机计算方便,但是人类和机器并不能直观地判断大小。所以选择加上 <span class="math inline">\(2^n\)</span> ,(这里的 <span class="math inline">\(n\)</span> 同样是不包含符号位的)。因此得到移码的定义 <span class="math display">\[[x]_{\text {移 }}= 2^{n}+x\left(2^{n}> x \geqslant-2^{n}\right)\]</span> 例如, <span class="math display">\[\begin{aligned}&{[+0]_{\text {移 }}= 2^{5}+0 = 1,00000} \\&{[-0]_{\text {移 }}= 2^{5}-0 = 1,00000}\end{aligned}\]</span></p><ul><li>可见在移码中 0 的表示唯一。</li><li>移码只有整数形式的定义,而无小数定义。因为移码在数据表示中负责浮点数的 <strong>阶码</strong> 部分,其只有整数。</li><li>最小值为全 0,最大值为全 1,非常直观</li><li><strong>同一真值的移码和补码只有符号位不同</strong></li></ul><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%A7%BB%E7%A0%81%E6%AF%94%E8%BE%83.png" alt="image-20220726224410850" style="zoom:80%;" / loading="lazy"></p><h2 id="数的定点表示和浮点表示">数的定点表示和浮点表示</h2><p>在计算机中,小数点 <strong>不用专门的器件表示,而是按约定的方式标出</strong>,共有两种方法表示小数点的存在,即 <strong>定点表示</strong> 和 <strong>浮点表示</strong>。定点表示的数称为定点数,浮点表示的数称为浮点数。</p><h3 id="定点表示">定点表示</h3><p>所谓定点表示,即小数点固定在某一位置。其有两种形式,分别是小数点在数符(正负号)后,和小数点在数值后。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%AE%9A%E7%82%B9%E8%A1%A8%E7%A4%BA.png" alt="image-20220727145024898" style="zoom:80%;" / loading="lazy"></p><ul><li>根据机器选择的小数点位置的不同,分为 <strong>小数定点机</strong> 和 <strong>整数定点机</strong></li></ul><table><thead><tr class="header"><th>定点机</th><th>小数定点机</th><th>整数定点机</th></tr></thead><tbody><tr class="odd"><td>原码</td><td><span class="math inline">\([-(1-2^{-n}),+(1-2^{-n})]\)</span></td><td><span class="math inline">\([-(2^n-1),+(2^n-1)]\)</span></td></tr><tr class="even"><td>补码</td><td><span class="math inline">\([-1,+(1-2^{-n})]\)</span></td><td><span class="math inline">\([-2^n,+(2^n-1)]\)</span></td></tr><tr class="odd"><td>反码</td><td><span class="math inline">\([-(1-2^{-n}),+(1-2^{-n})]\)</span></td><td><span class="math inline">\([-(2^n-1),+(2^n-1)]\)</span></td></tr></tbody></table><ul><li>不难发现,原码的范围与反码表示的范围一致,这也与上一篇的结论相符合。而补码的最小值会发生改变,也与上篇相符。</li></ul><h3 id="浮点表示">浮点表示</h3><p>实际上计算机中处理的数不一定是纯小数或纯整数(如圆周率),而且有些数据的数值范围相差很大,它们都不能直接用定点小数或定点整数表示,除非疯狂加长机器字长,这显然是不现实的。但其均可用浮点数表示。<strong>浮点数即小数点的位置可以浮动的数</strong>,比如: <span class="math display">\[\begin{aligned}352.47 &= 3.5247 \times 10^{2} \\&= 3524.7 \times 10^{-1} \\&= 0.35247 \times 10^{3}\end{aligned}\]</span> 其实就类似我们学过的科学计数法,其一般形式为 <span class="math inline">\(N = S \times r^j\)</span> ,其中 <span class="math inline">\(S\)</span> 称为 <strong>尾数</strong>, <span class="math inline">\(j\)</span> 称为 <strong>阶码</strong>, <span class="math inline">\(r\)</span> 称为 <strong>基值</strong>。</p><ul><li>尾数 <span class="math inline">\(S\)</span> 的绝对值小于等于 1,为小数</li><li>阶码 <span class="math inline">\(j\)</span> 为整数</li><li>基值 <span class="math inline">\(r\)</span> 在计算机中一般取 2 的次幂</li></ul><p>当 <span class="math inline">\(r = 2\)</span> 时,举个例子: <span class="math display">\[\begin{aligned}N &= 11.0101 \\&= 0.110101 \times 2^{10} \\&= 1.10101 \times 2^{1} \\&= 1101.01 \times 2^{-10} \\&= 0.00110101 \times 2^{100}\end{aligned}\]</span></p><ul><li>注意,这里的阶码同样为 2 进制。</li><li>注意,尾数的绝对值限制</li><li>将尾数最高位为 1 的数称为 <strong>规格化数</strong>,此时精度最高</li></ul><h4 id="浮点数的表现形式">浮点数的表现形式</h4><p>在设计计算机的过程中,如果事先约定好了基值 <span class="math inline">\(r\)</span> ,那么存储浮点数只需要记录另外两个数值即可:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%B5%AE%E7%82%B9%E6%95%B0%E5%BD%A2%E5%BC%8F.png" alt="image-20220727152107973" style="zoom:80%;" / loading="lazy"></p><ul><li><span class="math inline">\(j_f\)</span> 和 <span class="math inline">\(S_f\)</span> 表示正负</li><li><span class="math inline">\(n\)</span> ,尾数位数,即反映浮点数精度</li><li><span class="math inline">\(m\)</span> ,阶码位数,即反映浮点数范围</li></ul><h4 id="浮点数的表示范围">浮点数的表示范围</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%B5%AE%E7%82%B9%E8%8C%83%E5%9B%B41.png" alt="image-20220727153145219" style="zoom:80%;" / loading="lazy"></p><ul><li>上溢:阶码大于最大阶码,则报错进行中断溢出处理</li><li>下溢:阶码小于最小阶码,则按照机器 0 处理</li></ul><h4 id="浮点数的规格化">浮点数的规格化</h4><p>规格化形式与 <span class="math inline">\(r\)</span> 的取值有直接联系,当 <span class="math inline">\(r = 2\)</span> 时,要求首位为 1,当 <span class="math inline">\(r = 4\)</span> 时,要求首两位不全为 0。</p><p><strong>事实上,可以总结出当 <span class="math inline">\(r = 2^i\)</span> 时,要求首 <span class="math inline">\(i\)</span> 位不全为 0</strong>,推广到其他码制:</p><table><thead><tr class="header"><th>码制</th><th>S > 0 规格化形式</th><th>S < 0 规格化形式</th></tr></thead><tbody><tr class="odd"><td>真值</td><td>0.1XXX</td><td>-0.1XXX</td></tr><tr class="even"><td>原码</td><td>0.1XXX</td><td>1.1XXX</td></tr><tr class="odd"><td>补码</td><td>1.0XXX</td><td>1.0XXX</td></tr><tr class="even"><td>反码</td><td>0.1XXX</td><td>1.0XXX</td></tr></tbody></table><ul><li><strong>原码:无论正负首位都为 1</strong></li><li><strong>补码:符号位与首位不同</strong>,有特例 <span class="math inline">\([-\frac{1}{2}] _\text{补}= 1.100\)</span> 不是规格化数, <span class="math inline">\([-1]_\text{补} = 1.000\)</span> 是规格化数</li></ul><p>很容易想到,通过左移右移就可以使得浮点数规格化。这被称为 <strong>左规右规</strong>,但是要注意基值 <span class="math inline">\(r\)</span> 对于阶码的影响。</p><p>比如 <span class="math inline">\(r = 4\)</span> ,左规 2 位,尾数左移 2 位,阶码减 1。此时就不能左规 3 位,因为要保证阶码为整数。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%B5%AE%E7%82%B9%E4%BE%8B%E5%AD%90.png" alt="image-20220727160349750" style="zoom:80%;" / loading="lazy"></p><ul><li>值得注意的是,当一个浮点数尾数为 0 时,不论其阶码为何值;或阶码等于或小于它所能表 示的最小数时,不管其尾数为何值,机器都把该浮点数作为零看待,并称之为“机器零”。</li></ul><h3 id="定点数和浮点数的比较">定点数和浮点数的比较</h3><ol type="1"><li><p>当浮点机和定点机中,数的位数相同时,浮点数的表示范围比定点数的大得多。</p></li><li><p>当浮点数为规格化数时,其相对精度远比定点数高。</p></li><li><p>浮点数运算要分阶码部分和尾数部分,而且运算结果都要求规格化,故浮点运算步骤比定点运算步骤多,运算速度比定点运算的低,运算线路比定点运算的复杂。</p></li><li><p>在溢出的判断方法上,浮点数是对规格化数的阶码进行判断,而定点数是对数值本身进行判断。</p></li><li><p>总之,浮点数在数的表示范围、数的精度、溢出处理和程序编程方面(不取比例因子)均优于定点数。但在运算规则、运算速度及硬件成本方面又不如定点数。因此,究竟选用定点数还是浮点数,应根据具体应用综合考虑。一般来说,通用的大型计算机大多采用浮点数,或同时采用定、浮点数;小型、微型及某些专用机、控制机则大多采用定点数。当需要作浮点运算时,可通过软件实现,也可外加浮点扩展硬件(如协处理器)来实现。</p></li></ol><h3 id="ieee-754-标准">IEEE 754 标准</h3><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/IEEE.png" alt="image-20220727161122768" / loading="lazy"><figcaption aria-hidden="true">image-20220727161122768</figcaption></figure><ul><li>尾数必须规格化,非 0 的有效位最高位为 1(隐含)。所以尾数有一个最高位为隐藏位,其值恒为 1</li><li>在实际应用中分为短实数(float)和长实数(double)以及临时实数(不采用隐藏位)</li></ul><table><thead><tr class="header"><th>类型</th><th>符号位 S</th><th>阶码</th><th>尾数</th><th>总位数</th></tr></thead><tbody><tr class="odd"><td>短实数</td><td>1</td><td>8</td><td>23</td><td>32</td></tr><tr class="even"><td>长实数</td><td>1</td><td>11</td><td>52</td><td>64</td></tr><tr class="odd"><td>临时实数</td><td>1</td><td>15</td><td>64</td><td>80</td></tr></tbody></table><h2 id="定点运算">定点运算</h2><h3 id="移位运算">移位运算</h3><p>移位运算指 <strong>小数点不动,数据左移或者右移</strong>,其中左移绝对值变大,右移绝对值减小。对计算机来说,左移一位意味着数据变为原先的两倍,右移则为二分之一。移位运算有很大的实用价值。例如,当某计算机没有乘(除) 法运算线路时,可以采用移位和加法相结合,实现乘(除)运算。</p><h4 id="算术移位">算术移位</h4><blockquote><p>计算机中机器数的字长往往是固定的,当机器数左移 n 位或右移 n 位时,必然会使其 n 位低位或 n 位高位出现空位。那么,对空出的空位应该添补 0 还是 1 呢?这 <strong>与机器数采用有符号数还是无符号数有关</strong>。对有符号数的移位称为算术移位。</p></blockquote><table><thead><tr class="header"><th>真值</th><th>码制</th><th>填补代码</th></tr></thead><tbody><tr class="odd"><td>正数</td><td>原码、反码、补码</td><td>0</td></tr><tr class="even"><td>负数</td><td>原码</td><td>0</td></tr><tr class="odd"><td>负数</td><td>反码</td><td>1</td></tr><tr class="even"><td>负数</td><td>补码</td><td>左移为 0,右移为 1</td></tr></tbody></table><ul><li>算术移位保证 <strong>符号位不变</strong>,约定数值部分移位后能够存储的下</li><li>机器数为正时,不论是左移还是右移,添补代码均为 0。</li><li>由于负数的 <strong>原码数值部分与真值相同</strong>,故在移位时只要使符号位不变,其空位均添 0 即可。</li><li>由于负数的 <strong>反码各位除符号位外与负数的原码正好相反</strong>,故移位后所添的代码应与原码相反,即全部添 1。</li><li>分析任意负数的补码可发现,当对其由低位向高位找到第一个“1”时,在此“1”左边的各位均与对应的反码相同,而在此“1”右边的各位(包括此“1”在内)均与对应的原码相同。故负数的补码左移时,因空位出现在低位,则添补的代码与原码相同,即添 0;右移时因空位出现在高位,则添补的代码应与反码相同,即添 1。</li><li>对于负数,三种机器数算术移位后符号位均不变。负数的原码左移时,高位丢 1,结果出错;右移时,低位丢 1,影响精度。负数的补码左移时,高位丢 0,结果出错;右移时,低位丢 1,,影响精度。负数的反码左移时,高位丢 0,结果出错;右移时,低位丢 0,影响精度。</li></ul><h4 id="逻辑移位">逻辑移位</h4><p>有符号数的移位称为算术移位,无符号数的移位称为逻辑移位。换言之,算术移位不会移动符号位,而逻辑移位会。逻辑移位的规则是:逻辑左移时,高位移丢,低位添 0;逻辑右移时,低位移丢,高位添 0。</p><h3 id="加减运算">加减运算</h3><p>之所以引进补码,就是因为其可以将减法运算转换为加法运算。而现代计算机也确实采用补码做加减法运算</p><h4 id="补码加减法">补码加减法</h4><p>整数加法: <span class="math inline">\([A] _{\text {补 }}+[B]_{\text {补 }}= [A+B]_{\text {补 }}\left(\bmod 2^{n+1}\right)\)</span></p><p>小数加法: <span class="math inline">\([A] _{\text {补 }}+[B]_{\text {补 }}= [A+B]_{\text {补 }}(\bmod 2)\)</span></p><p>整数减法: <span class="math inline">\([A-B] _{\text {补 }}= [A+(-B)]_{\text {补 }}= [A] _{\text {补 }}+[-B]_{\text {补 }}\left(\bmod 2^{n+1}\right)\)</span></p><p>小数减法: <span class="math inline">\([A-B] _{\text {补 }}= [A+(-B)]_{\text {补 }}= [A] _{\text {补 }}+[-B]_{\text {补 }}(\bmod 2)\)</span></p><ul><li>注意 <strong>符号位需要参与计算</strong>,丢弃符号位的进位</li></ul><h4 id="溢出判断">溢出判断</h4><ol type="1"><li><p>一位符号位判断:参加操作的两个数(减法时即为被减数和“求补” 以后的减数)符号相同,其结果的符号与原操作数的符号不同,即为溢出。换言之,数值部分最高位的进位 <span class="math inline">\(\oplus\)</span> 符号位的进位 = 1 即为溢出。</p></li><li><p>两位符号位判溢出:首先使用变形补码,其公式如下。简而言之,对于正数其变形补码(符号位)为 <span class="math inline">\(00\)</span> ,对于负数其变形补码(符号位)为 <span class="math inline">\(11\)</span> 。数值部分可以用原本的取反加一计算。 <span class="math display">\[[x]_\text{补}= \begin{cases}x & 1 > x \geq 0 \\ 4+x & 0 > x \geq-1(\bmod 4)\end{cases}\]</span></p></li></ol><p><span class="math display">\[[x]_{\text {补 }}= \begin{cases}0,\ x & 2^{n}> x \geqslant 0 \\ 2^{n+2}+x & 0 > x \geqslant-2^{n} \quad\left(\bmod 2^{n+2}\right)\end{cases}\]</span></p><ul><li>在用变形补码作加法时,2 位符号位要连同数值部分一起参加运算,而且 <strong>高位符号位产生的进位自动丢失</strong>,便可得正 确结果。变形补码判断溢出的原则是:<strong>当 2 位符号位不同时,表示溢出</strong>,否则,无溢出。不论是否发生溢出,高位符号位永远代表其结果真正的符号。</li></ul><h3 id="乘法运算">乘法运算</h3><p>先来看一个乘法的改进算法,其计算 <span class="math inline">\(0.1101 \times 0.1011\)</span> 的值,具体过程 <a href="https://www.bilibili.com/video/BV1t4411e7LH?p=85">哈工大计组 p85</a>。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B9%98%E6%B3%95%E6%94%B9%E8%BF%9B.png" alt="image-20220730234454703" style="zoom:80%;" / loading="lazy"></p><p>再通过异或电路判断正负,结果为 <span class="math inline">\(0.10001111\)</span></p><p>通过乘法运算的改进算法可归纳如下:</p><ol type="1"><li><strong>符号位单独判断。</strong></li><li><strong>乘法运算可用逻辑移位和加法来实现</strong>,比如两个 4 位数相乘,总共需要进行 4 次加法运算和 4 次 <strong>逻辑移位</strong>。</li><li>由乘数的末位值确定被乘数是否与原部分积相加,然后右移一位,形成新的部分积;同时,乘数也右移一位,<strong>空出高位存放部分积的低位。</strong></li><li>每次做加法时,被乘数仅仅与原部分积的高位(被乘数的位数)相加,<strong>其低位被移至乘数所空出的高位位置</strong>。</li></ol><p>计算机很容易实现这种运算规则。用一个 <strong>X 寄存器</strong> 存放被乘数,一个 <strong>ACC 寄存器</strong> 存放乘积的高位,另一个 <strong>MQ 寄存器</strong> 存放乘数及乘积的低位,再配上加法器及其他相应电路,就可组成乘法器。又因加法只在部分积的高位进行,故不但节省了器材,而且还缩短了运算时间。</p><blockquote><ul><li><p>ACC:累加器,用于存放操作数,或运算结果。</p></li><li><p>MQ:乘商寄存器,在乘、除运算时,用于存放操作数或运算结果。</p></li><li><p>X:通用的操作数寄存器,用于存放操作数</p></li><li><p>ALU:算术逻辑单元,通过内部复杂的电路实现算数运算、逻辑运算</p></li></ul></blockquote><h4 id="原码一位乘法">原码一位乘法</h4><ol type="1"><li><p>符号位用异或单独判断</p></li><li><p>数值部分按照绝对值相乘(先加再 <strong>逻辑右移</strong>)</p></li><li><p>每次加法根据 <strong>MQ 寄存器</strong>(存放乘数及乘积低位)的末位确定:</p><ul><li><p>1: <span class="math inline">\(ACC+[\lvert {x}\rvert]_\text{原}\)</span></p></li><li><p>0: $ACC + 0 $</p></li></ul></li><li><p>用右移的次数(等于乘数的位数,或者机器字长)判断是否结束</p></li></ol><h4 id="补码一位乘法">补码一位乘法</h4><ol type="1"><li>符号位参与运算</li><li>先加再 <strong>补码的算术右移</strong>,最后再加</li><li>辅助位:指 <strong>MQ 寄存器</strong> 扩展的一位末位,其初始值为 0。每次右移时其值被更新为末位的值</li><li><strong>ACC 寄存器</strong> 和 <strong>X 寄存器</strong> 存放的乘积高位和被乘数,都是用 <strong>双符号位记录</strong></li><li>每次加法根据 <strong>MQ 寄存器</strong> 的末位和 <strong>辅助位</strong> 确定,使用辅助位 - MQ 最低位的值判断:<ul><li>1: <span class="math inline">\(ACC+[x]_\text{补}\to ACC\)</span></li><li>0: <span class="math inline">\(ACC+0\to ACC\)</span></li><li>-1: <span class="math inline">\(ACC+[-x]_\text{补}\to ACC\)</span></li></ul></li></ol><h3 id="除法运算">除法运算</h3><p>回忆一下手算除法,不难总结出二进制手算除法的规律:使用数值部分进行计算,根据除数和余数的大小确定一位商,进行一次减法操作并得到余数。再余数后补零,重复计算直到满足精度或者整除。那么如何在计算机中实现上述过程呢?</p><p>为了方便说明,定义被除数 <span class="math inline">\(x\)</span> ,除数 <span class="math inline">\(y\)</span> ,当前的余数 <span class="math inline">\(R\)</span></p><h4 id="原码恢复余数法">原码恢复余数法</h4><ol type="1"><li>符号位用异或单独判断</li><li>数值部分按照 <strong>绝对值</strong> 进行计算,还需要计算除数的绝对值补码和除数相反数的绝对值补码。即 <span class="math inline">\([\lvert{y}\rvert] _\text{补}\)</span> 和 <span class="math inline">\([\lvert{-y}\rvert]_\text{补}\)</span></li><li>使用 <strong>ACC</strong> 存储被除数(当前的余数 <span class="math inline">\(R\)</span> ),使用 <strong>X</strong> 存储除数 <span class="math inline">\(y\)</span> ,使用 <strong>MQ</strong> 存储商</li><li>这个方法的精髓就在于:计算机不会直接比较 <span class="math inline">\(R\)</span> 和 <span class="math inline">\(y\)</span> 的大小,而是直接 <strong>商 1</strong>(在 MQ 的末位写入)。然后计算 $ R - y = R + []_ ACC$ ,将结果写入 <strong>ACC</strong> 后,通过电路判断符号位是否为 0(正数)。如果为 1,则进行 $ R + y ACC$ 恢复余数,再改成 <strong>商 0</strong>。</li><li>对 <strong>ACC</strong> 和 <strong>MQ</strong> 进行 <strong>逻辑左移</strong>,<strong>MQ</strong> 的最高位填充到 <span class="math inline">\(ACC\)</span> 的末位,末位补零。<strong>最后一次上商后无需左移</strong></li><li>重复 4-5 步,直到商的位数达到机器字长长度。假设得到余数为 00111,则真值为 <span class="math inline">\(0.0111 \times 2^{-n} = 0.0111 \times 2^{-4}\)</span> 。商为 <span class="math inline">\(01101\)</span> ,则商为 <span class="math inline">\(0.1101\)</span> 。再用符号位异或确定商的符号位(余数一定是正数)。</li></ol><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%9B%9E%E5%A4%8D%E4%BD%99%E6%95%B0%E6%B3%95.png" alt="image-20220803184738585" style="zoom:80%;" / loading="lazy"></p><h4 id="原码加减交替法">原码加减交替法</h4><p>对恢复余数法进行总结可以发现:</p><ul><li>上商 1 后: <span class="math inline">\(2 \times R -y\)</span></li><li>上商 0 后: <span class="math inline">\(2 \times (R+y) -y = 2 \times R +y\)</span></li></ul><p>通过这样的化简,我们可以精简恢复余数法上商 0 的操作,即 <strong>省略恢复余数的步骤</strong>。故又称不恢复余数法。同样也是移位 n 次,上商 n+1 次。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%8A%A0%E5%87%8F%E4%BA%A4%E6%9B%BF%E6%B3%95.png" alt="image-20220803191510289" style="zoom:80%;" / loading="lazy"></p><h4 id="补码除法">补码除法</h4><ol type="1"><li>使用双符号位表示正负,符号位参与运算</li><li>被除数 <span class="math inline">\(x\)</span> 和除数 <span class="math inline">\(y\)</span> 同号,则 <span class="math inline">\(R = x -y = x+[-y] _\text{补}\)</span> ,否则 <span class="math inline">\(R = x+y = x+ [y]_\text{补}\)</span></li><li>若余数 <span class="math inline">\(R\)</span> 与除数 <span class="math inline">\(y\)</span> 同好,则商 1,余数左移一位减去除数 <span class="math inline">\(2 \times R - y = 2 \times R +[-y] _\text{补}\)</span> ,否则商 0,余数左移一位加上除数 <span class="math inline">\(2 \times R + y = 2 \times R +[y]_\text{补}\)</span> 。重复 n 次</li><li>如果对商的精度没有特殊要求,一般可采用“末位恒置 1”法,这种方法操作简单,易于实现,而且最大误差仅为 <span class="math inline">\(2^{-n}\)</span></li></ol><h2 id="浮点四则运算">浮点四则运算</h2><h3 id="浮点加减运算">浮点加减运算</h3><blockquote><p>由于浮点数尾数的小数点均固定在第一数值位前,所以尾数的加减运算规则与定点数的完全相同。但由于其阶码的大小又直接反映尾数有效值小数点的实际位置,因此当两浮点数阶码不等时,因两尾数小数点的实际位置不一样,尾数部分无法直接进行加减运算。为此,浮点数加减运算必须按以下几步进行。</p></blockquote><ol type="1"><li>对阶:使两数的小数点对齐</li><li>尾数求和:将对阶后的两尾数按定点加减运算规则求和(差)。</li><li>规格化,为增加有效数字的位数,提高运算精度,必须将求和(差)后的尾数规格化。</li><li>舍入,为提高精度,要考虑尾数右移时丢失的数值位。</li><li>溢出判断,即判断结果是否溢出。</li></ol><p>例子: <span class="math inline">\(x = 0.1101 \times 2^{01} \quad y =(-0.1010) \times 2^{11}\)</span> ,计算 <span class="math inline">\(x+y\)</span></p><h4 id="对阶">对阶</h4><p>对阶的目的是使两操作数的小数点位置对齐,即使两数的阶码相等。为此,首先要求出 <strong>阶差</strong>,再按 <strong>小阶向大阶看齐</strong> 的原则,使阶小的尾数向右移位,每右移一位,阶码加 1,直到两数的阶码相等为止。<strong>右移的次数正好等于阶差</strong>。尾数右移时可能会发生数码丢失,影响精度。(如果选择大阶向小阶看齐,则需要让尾数变大左移,这可能导致最高位丢失造成数据错误)</p><p>首先将数据用浮点数补码表示: <span class="math display">\[[x]_\text{补} = 00,01;\ 00.1101\\[y]_\text{补} = 00,11;\ 11.0110\]</span></p><ul><li>四位阶码(两位阶符),六位尾数(两位尾符)</li></ul><p>求阶差: <span class="math display">\[[\Delta j] _\text{补} = [j_x]_\text{补} - [j_y]_\text{补} = 00,01 + 11,01 = 11,10 < 0\]</span></p><ul><li>阶差为 <span class="math inline">\(11,10 = -2\)</span> ,所以 <span class="math inline">\(j_x+2\quad S_x \to 2\)</span></li></ul><p>对阶: <span class="math inline">\([x] _\text{补'} = 00,11; 00.0011 \quad [y]_\text{补} = 00,11;\ 11.0110\)</span></p><h4 id="尾数求和">尾数求和</h4><p>将对阶后的两个尾数按定点加(减)运算规则进行运算 <span class="math display">\[[S_x +S_y] _\text{补} = [S_x]_\text{补'}+[S_y]_\text{补} = 00.0011 + 11.0110 = 11.1001\]</span></p><ul><li><span class="math inline">\([x+y]_\text{补} = 00,11; 11.1001\)</span></li></ul><h4 id="规格化">规格化</h4><p>当前基值 <span class="math inline">\(r\)</span> 为 2,那么要求前 1 位即首位不为 0。通过 <strong>左规和右规</strong> 进行规格化,直到尾符和首位不同为止(机器数为补码,且考虑特例 <span class="math inline">\(-1 \ and\ -\frac{1}{2}\)</span> )</p><p>左规:尾数左移,阶码减一 <span class="math display">\[[x+y]_\text{补} = 00,10; 11.0011\]</span> 右规:尾数右移,阶码加一。只有当尾数的符号位溢出,俩符号位数值不等,即形如 <span class="math inline">\(10.XXX \quad 01.XXX\)</span> 时才进行右规</p><h4 id="舍入">舍入</h4><p>在 <strong>对阶和右规</strong> 的过程中,可能会将尾数的低位丢失,引起误差,影响精度。为此可用舍入法来提高尾数的精度。常用的舍入方法有以下两种。</p><ol type="1"><li>“0 舍 1 人”法:类似于十进制数运算中的“四舍五入”法,即在尾数右移时,被移去的最高数值位为 0,则舍去;被移去的最高数值位为 1,则在尾数的末位加 1。这样做可能使尾数又溢出,此时需再做一次右规。</li><li>“恒置 1”法:尾数右移时,不论丢掉的最高数值位是“1”或“0”,都使右移后的尾数末位恒置“1”。这种方法同样有使尾数变大和变小的两种可能。</li></ol>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>绝区零好潮。。。</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220808231206.jpeg" alt="img" style="zoom:80%;" /></p></summary>
<category term="计算机组成原理" scheme="http://lapras.xyz/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="硬件" scheme="http://lapras.xyz/tags/%E7%A1%AC%E4%BB%B6/"/>
</entry>
<entry>
<title>计算机组成原理笔记(五)</title>
<link href="http://lapras.xyz/2022/07/26/fe0a25d8.html"/>
<id>http://lapras.xyz/2022/07/26/fe0a25d8.html</id>
<published>2022-07-26T07:53:07.000Z</published>
<updated>2022-08-21T07:21:56.353Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>难起来了,光看哈工大的 mooc 感觉已经听不懂了,准备去啃啃 CSAPP 或者 Nand2Tetris 。</p><span id="more"></span><h2 id="概述">概述</h2><h3 id="输入输出系统的发展概况">输入输出系统的发展概况</h3><p>早期的计算机数目较少,应用少,外设也少。所以早期使用 <strong>分散连接</strong> 的方式,即每一个设备都有专门的控制电路,甚至与 CPU 的控制电路高度耦合。所以对外设增删都非常麻烦。外部设备与主机的连接主要采用 <strong>程序查询方式</strong>,即 CPU 和 I/O 设备 <strong>串行工作</strong>。</p><p>随着计算机发展,分散连接逐步被淘汰。出现了 <strong>I/O 接口</strong> 与 <strong>DMA 控制器</strong>,采用 <strong>总线</strong> 方式进行连接,一条总线连接多个设备,总线与 I/O 设备的传输方式也变成了 <strong>并行工作</strong>。</p><p>为了进一步使得 I/O 操作独立于 CPU 之外,又出现了具有 <strong>通道结构</strong> 的 I/O,可以看成 <strong>小型的 DMA 控制器</strong>,具有自己的指令系统,可以控制连接在通道上的 I/O 设备直接与主机交互。</p><p>现代的超级计算机可以使用专门的处理器作为 I/O 处理机,甚至能帮助 CPU 进行运算。</p><h3 id="输入输出系统的组成">输入输出系统的组成</h3><p><strong>I/O 软件</strong></p><ol type="1"><li>I/O 指令:CPU 指令集的一部分,其格式为操作码+命令码+设备码</li><li>通道指令:通道能够指令由通道指令构成的程序,指出数组的首地址、传送字数、操作命令</li></ol><p><strong>I/O 硬件</strong></p><ol type="1"><li>设备–I/O 接口–总线–主机</li><li>设备–设备控制器–子通道–通道–主机</li></ol><h3 id="io-设备与主机的联系方式">I/O 设备与主机的联系方式</h3><p>I/O 设备是有地址的,主机必须要给出其地址才能操作。所以要对 I/O 设备编址,其编址方式有:</p><p><strong>统一编址</strong>:将 I/O 设备地址编写在内存区域,用取数和读数到对应区域则为 I/O 操作。显然会节省命令集,但是对于寻址空间的要求较高。</p><p><strong>单独编址</strong>:在内存地址之外,专门设置一个地址空间给外部设备。为了区分指令是对内存还是对 I/O 设备,此时需要有专门的 I/O 指令进行操作。</p><p>编址之后,需要选址。采用 <strong>设备选择电路</strong> 识别是否被选中,只要把 CPU 给出的地址和设备中保存的地址进行比较,相同则为选中。然后就可以开始数据传送:</p><p><strong>串行</strong>:数据一位一位进行传输,传输速度慢,适合远程上传输</p><p><strong>并行</strong>:同时有多位数据进行传输,通常情况下为 8 的倍数。</p><p>联络方式:外设接受或发送数据的响应情况</p><p><strong>立即响应</strong>:直接发生响应</p><p><strong>异步工作</strong>:一般采用应答信号的方式,分为并行传输和串行传输。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%80%E8%88%AC%E9%87%87%E7%94%A8%E5%BA%94%E7%AD%94%E4%BF%A1%E5%8F%B7%E7%9A%84%E6%96%B9%E5%BC%8F.png" alt="image-20220724133812663" style="zoom:80%;" / loading="lazy"></p><p><strong>同步工作</strong>:采用同步时标</p><h3 id="io-设备与主机信息传送的控制方式">I/O 设备与主机信息传送的控制方式</h3><p>程序查询方式:CPU 和外设串行工作</p><p>程序中断方式:CPU 和外设做到部分并行</p><p>DMA 方式:使得外部设备和内存之间建立直接连接,无需 CPU 额外处理</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%A8%8B%E5%BA%8F%E6%9F%A5%E8%AF%A2%E6%96%B9%E5%BC%8F.png" alt="image-20220724135731680" style="zoom:80%;" / loading="lazy"></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%A8%8B%E5%BA%8F%E4%B8%AD%E6%96%AD.png" alt="image-20220724140401331" style="zoom:80%;" / loading="lazy"></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%A8%8B%E5%BA%8F%E4%B8%AD%E6%96%AD2.png" alt="image-20220724140459014" style="zoom:80%;" / loading="lazy"></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/DMAa.png" alt="image-20220724141208316" style="zoom:80%;" / loading="lazy"></p><h2 id="外部设备">外部设备</h2><h3 id="概述-1">概述</h3><p>外部设备(I/O 设备)通过 I/O 接口与主机相连,其主要包含两个部分:设备控制器与物理部分。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%A4%96%E9%83%A8%E8%AE%BE%E5%A4%87.png" alt="image-20220725101402068" style="zoom:80%;" / loading="lazy"></p><h3 id="外部设备的分类">外部设备的分类</h3><ol type="1"><li>人机交互设备:键盘、鼠标、打印机、显示器</li><li>计算机信息存储设备:磁盘、光盘、磁带</li><li>机-机通信设备:调制解调器</li></ol><h2 id="io-接口">I/O 接口</h2><h3 id="接口的功能">接口的功能</h3><ol type="1"><li>实现设备的选择</li><li>实现数据的缓冲平衡不同外部设备速度匹配</li><li>实现数据串-并格式转换</li><li>实现电平转换</li><li>传送控制命令</li><li>反映设备的工作状态。</li></ol><h3 id="接口的组成">接口的组成</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8E%A5%E5%8F%A3%E7%9A%84%E7%BB%84%E6%88%90.png" alt="image-20220725103240038" style="zoom:80%;" / loading="lazy"></p><ul><li>设备选择线(单向):传送本次参与信息传输的设备码(设备地址)或端口地址给 I/O 接口进行匹配</li><li>数据线(双向):完成数据的输入输出</li><li>命令线(单向):主机发送命令进过缓冲和译码之后,控制设备操作</li><li>状态线(单向):从 I/O 接口发送给主机,告知设备的状态</li></ul><h3 id="接口的功能和组成">接口的功能和组成</h3><table><thead><tr class="header"><th>功能</th><th>组成</th></tr></thead><tbody><tr class="odd"><td>选址功能</td><td>设备选择电路</td></tr><tr class="even"><td>传送命令</td><td>命令寄存器、命令译码器</td></tr><tr class="odd"><td>传送数据</td><td>数据缓冲寄存器</td></tr><tr class="even"><td>反映设备状态</td><td>设备状态标记</td></tr></tbody></table><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/I/O%E6%8E%A5%E5%8F%A3%E7%BB%84%E6%88%90.png" alt="image-20220725104935351" style="zoom:80%;" / loading="lazy"></p><h3 id="接口分类">接口分类</h3><ol type="1"><li>数据传送方式分类:并行接口、串行接口</li><li>选择的灵活性分类:可编程接口、不可编程接口</li><li>通用性分类:通用接口、专用接口</li><li>数据传送的控制方式:中断方式接口、DMA 接口</li></ol>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>难起来了,光看哈工大的 mooc 感觉已经听不懂了,准备去啃啃 CSAPP 或者 Nand2Tetris 。</p></summary>
<category term="计算机组成原理" scheme="http://lapras.xyz/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="硬件" scheme="http://lapras.xyz/tags/%E7%A1%AC%E4%BB%B6/"/>
</entry>
<entry>
<title>计算机组成原理笔记(四)</title>
<link href="http://lapras.xyz/2022/07/23/9194e09f.html"/>
<id>http://lapras.xyz/2022/07/23/9194e09f.html</id>
<published>2022-07-23T05:58:07.000Z</published>
<updated>2022-08-21T07:21:56.356Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>摸了一周崛起,又回来上工了!</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220723135707.png" alt="image-20220723135707436" style="zoom:80%;" / loading="lazy"></p><span id="more"></span><h2 id="概述">概述</h2><h3 id="问题的提出">问题的提出</h3><p>CPU 的发展速度非常快,而存储器的速度则会成为瓶颈,则 CPU 存在 <strong>空等</strong> 现象。通过在 CPU 和主存之间增加一个 Cache 缓存,其由静态 RAM 组成,和主存相比容量小,速度快。</p><blockquote><p>Cache 之所以行之有效,就不得不提 <strong>程序访问的局部性原理</strong>,这个原理的内含分两部分,一是时间的局部性:当前访问到的指令和数据在不久的将来很有可能还会被访问到;二是空间的局部性:当前访问的指令和数据的附近的指令和数据在不久的将来很可能会被访问到。因此,如果我们把当前访问的指令、数据及其附近的指令和数据都缓存到 Cache,那么之后再访问时,CPU 就无需访存了,进而提升了系统的性能。</p></blockquote><h3 id="主存和缓存的编址">主存和缓存的编址</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/Cache%E7%BB%93%E6%9E%84.png" alt="Cache结构" style="zoom:80%;" / loading="lazy"></p><ul><li>内存被分为 <span class="math inline">\(M\)</span> 块,缓存被分为 <span class="math inline">\(C\)</span> 块,其块的大小相同都为 <span class="math inline">\(B\)</span> 个字。通常 <span class="math inline">\(M \gg C\)</span> ,这意味着同一时间内,只有一小部分的主存会被缓存</li><li>主存中的数据都是按块被缓存的,当某块被缓存时,由硬件为 Cache 的每一块维护的标记会记录下被缓存的主存块的块号。之后 CPU 访问该块的数据时,在缓存的标记中可以找到该块的块号,且相应 <strong>缓存块有效</strong>,那么就不需要访存了,直接访问相应的缓存块即可</li></ul><h3 id="缓存的命中率">缓存的命中率</h3><p>所谓 Cache 的命中,就是指 CPU 访问某个指令或数据时,其对应的主存块已经被写入缓存(已建立了对应关系/标记),反之则为未命中,CPU 必须到主存中去获取对应指令。</p><blockquote><p>CPU 多次访问数据,其中缓存命中的比率称为 Cache 的 <strong>命中率</strong>。命中率与 Cahce 的容量和块的大小有关,一般来说容量越大,块越大,则命中率越高。当然,凡是不能极端,缓存过大会提升成本和功耗,而块过大会减少块的数量,进而降低同一时间能够被缓存的主存块的数量。</p></blockquote><p>总的来说,命中率 与 Cache 的 容量与块长有关。一般块长取一个存取周期内从主存调出的信息长度</p><h4 id="访问效率-e">访问效率 <span class="math inline">\(e\)</span></h4><p><span class="math display">\[\begin{align*}e &= \frac{t}{\overline t}*100\% \\&=\frac{t_c}{h *t_c+(1-h)* t_m}*100\% \end{align*}\]</span></p><ul><li><span class="math inline">\(t\)</span> 为访问 Cache 的时间, <span class="math inline">\(\overline t\)</span> 为平均访问时间</li><li>带入命中率 <span class="math inline">\(h\)</span> 后,t_c <span class="math inline">\(为访问 Cache 的时间,\)</span> t_m$为访问主存的时间</li></ul><h3 id="cache-的基本结构">Cache 的基本结构</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/Cache%E7%9A%84%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84.png" alt="image-20220721105528285" style="zoom:80%;" / loading="lazy"></p><h3 id="cache-读写操作">Cache 读写操作</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/Cache%E8%AF%BB.png" alt="image-20220721110412863" style="zoom:80%;" / loading="lazy"></p><p>写操作主要有两种方式:</p><ul><li>写直达法:写操作时既写入 Cache 也写入主存,写操作时间即为访问主存的时间,Cache 块退出时,不需要对主存执行写操作,因此 Cache 块的更新策略比较容易实现。</li><li>写回法:写操作时只把数据写入 Cache 而不写入主存,写操作时间即为访问 Cache 的时间,当 Cache 数据被替换出去时需要写回主存,因此增加了 Cache 的复杂性。</li></ul><h3 id="cache-改进">Cache 改进</h3><ul><li>增加 Cache 的层次级数:Cache 本质上通常是 SRAM,不过不同的单元电路组成的 SRAM 是有区别的,有的速度快、功耗高、成本高,有的速度慢、功耗低、成本低、且易于集成(具体内容可搜索 LVT HVT 关键字)。因此,参照整个存储系统的层次结构,Cache 也可以细分出不同层次,比如 L1 Cache、L2 Cache 等,以此来获得性能、功耗、芯片面积、成本等方面的平衡。</li><li>参照哈佛架构分立缓存:采用独立的数据 Cache 和指令 Cache。现代处理器基本上都采用了流水线结构,如果单独为数据和指令设置 Cache,则在某条指令执行需要访问数据时,不会影响后面的指令的取指(一个是访问数据 Cache,一个是访问指令 Cache),从而提高计算机的性能。</li></ul><h2 id="主存地址映射">主存地址映射</h2><h3 id="直接映射">直接映射</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%9B%B4%E6%8E%A5%E6%98%A0%E5%B0%84.png" alt="image-20220721112338281" style="zoom:80%;" / loading="lazy"></p><p>首先要对主存按照缓存块的大小进行划分成不同的区,每个区的第一个字块都放置于 Cache 存储体中的第 0 块。CPU 给出的主存地址可以分成三个部分:区号(主存字块标记),块号(Cache 字块地址),偏移地址(字块内地址)。这种结构的优点是结构简单、速度快,缺点是在 Cache 有很多空闲的情况下仍出现 Cache 冲突,影响 Cache 利用率。</p><h3 id="全相联映射">全相联映射</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%85%A8%E7%9B%B8%E8%81%94%E6%98%A0%E5%B0%84.png" alt="全相联映射" style="zoom:80%;" / loading="lazy"></p><p>从全相联的结构可知,这种结构相较直接映射能够提高 Cache 的利用率,因为一个主存块可以被缓存到任一缓存块。但缺点也正是源于此,无法根据主存地址确定地址所在主存块会被缓存到哪个缓存块,因此在检查缓存是否命中时,需要多个比较器同时比较主存地址中主存块的块号和所有缓存标记,造成电路结构复杂,功耗也更高。</p><h3 id="组相联映射">组相联映射</h3><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%BB%84%E7%9B%B8%E8%81%94%E6%98%A0%E5%B0%84.png" alt="image-20220721115852678" style="zoom:80%;" / loading="lazy"></p><p>直接映射中,一个主存块只可能被缓存到某个特定的缓存块;全相联映射正好相反,一个主存块可以被缓存到任一缓存块。可以说这两种方式走了两个极端,各有优缺点,而组相联映射是前两种映射方式的折中,实现了一个主存块可以被缓存到若干个缓存块(<strong>一组缓存块</strong>)中:</p><h2 id="替换算法">替换算法</h2><h3 id="fifo">FIFO</h3><p>先进先出置换算法。这个就是类似于队列,先装入的页面先被置换掉。易于实现但是有可能淘汰频繁使用的页面,效果不好。</p><h3 id="lru">LRU</h3><p>将近期内最久末被访问过的 Cache 块置换出去。</p><p>LRU 算法是指: 会为每一个 Cache 块设置一个“计数器”,用于记录每个 Cache 块究竟有多长时间没有被访问了。在替换时直接选取“计数器”最大的替换即可。</p><ul><li>命中时,所命中的行的计数器清零,比其低的计数器+1,其余不变</li><li>未命中且还有空闲行时,新装入的行的计数器置为 0,其余非空闲行全+1</li><li>未命中且没有空闲行时,计数器最大的行的信息块被淘汰,新装入行的计数器置为 0,其余全+1</li></ul><h3 id="lfu">LFU</h3><p>将一段时间内被访问次数最少的那块从 Cache 中置换出去</p><p>LFU 算法会为每一个 Cache 块设置一个计数器,用于记录每个 Cache 块被访问过几次,当 Cache 块满后会替换计数器最小的</p><ul><li>新调入的块计数器为 0,之后每访问一次计数器就+1。需要替换时,选择计数器最小的一行替换</li><li>若有多个计数器最小的行,可以按照行号递增或 FIFO 策略进行选择</li></ul><h3 id="随机替换">随机替换</h3><p>随机确定将哪块从 Cache 中替换出去。 ## 辅助存储器</p><p>其不能直接与 CPU 进行信息交换,最常用的辅助存储器是 <strong>磁表面辅助存储器</strong></p><h3 id="磁表面辅助存储器">磁表面辅助存储器</h3><h4 id="技术指标">技术指标</h4><ol type="1"><li>记录密度:<ol type="1"><li>道密度 <span class="math inline">\(D_t\)</span> :沿磁盘半径方向单位长度上的磁道数,单位为道/英寸</li><li>位密度 <span class="math inline">\(D_b\)</span> :磁道单位长度上能记录的二进制代码位数,单位为位/英寸</li></ol></li><li>存储容量:一个硬盘存储器所能存储的字节长度, <span class="math inline">\(C = n \times k \times s\)</span></li><li>平均存取时间:存取时间是指从发出读写命令后。磁头从某一起始位置移动至新的记录位置,到开始从盘片表而读出或写入信息所需要的时间。<strong>这段时间由两个数值决定,一个是将磁头定位至所要求的磁道所需的时间,称为定位时间或寻道时间:另一个是寻道完成后至磁道上需要访问的信息到达磁头下的时间,称为等待时间</strong>,这两个时间都是随机变化的,因此往往使用平均值来表示。平均存取时间等于平均寻道时间与平均等待时间之和。平均寻道时间是最大寻道时间与最小寻道时间的平均值。</li><li>数据传输率:磁盘存储器在单位时间内向主机传送数据的字节数,叫数据传输率, 传输率与存储设备和主机接口逻辑有关。从存储设备考虑,假设磁盘旋转速度为 <span class="math inline">\(n\)</span> 转/秒,每条磁道容量为 <span class="math inline">\(N\)</span> 个字节,则数据传输率 <span class="math inline">\(D_r = D_b \times V(Bps)\)</span> , 其中 <span class="math inline">\(D_b\)</span> 为位密度, <span class="math inline">\(V\)</span> 为磁盘旋转的线速度。</li><li>误码率:出错的信息位数与读出信息位总数之比</li></ol><h4 id="磁记录原理">磁记录原理</h4><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%A3%81%E7%9B%98%E5%86%99.png" alt="image-20220723131552620" style="zoom:80%;" / loading="lazy"></p><p>通过写线圈通入方向不一样的电流来写入改变局部磁化单元的朝向,从而记录 0 与 1</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%A3%81%E7%9B%98%E8%AF%BB.png" alt="image-20220723131807182" style="zoom:80%;" / loading="lazy"></p><p>读写头在磁场中运动,切割磁力线,产生不同方向的电流,磁通与电势都发生变化,从而读取 0 和 1。</p><h3 id="硬磁盘存储器">硬磁盘存储器</h3><p>硬磁盘存储器的类型:</p><ol type="1"><li>固定磁头和移动磁头</li><li>可换盘和固定盘</li></ol><p>硬磁盘存储器的结构:</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%A1%AC%E7%A3%81%E7%9B%98%E7%BB%93%E6%9E%84.png" alt="image-20220723132957919" style="zoom:80%;" / loading="lazy"></p><ul><li>磁盘控制器接受来自主机的指令,转换为磁盘驱动器的控制命令。实现主机与驱动器之间的数据格式转换</li><li>盘片由硬质铝合金材料制成</li></ul><h3 id="软磁盘存储器">软磁盘存储器</h3><p>时代的眼泪,就速度而言不如硬盘,磁头来说软盘的磁头都是活动的,盘片为可更换,价格低廉但是容易损坏。</p><h3 id="光盘存储器">光盘存储器</h3><p>采用光存储技术,利用激光进行读写,第一代技术采用非磁性介质,不可擦写。第二代技术采用磁性介质后可以擦写。</p>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>摸了一周崛起,又回来上工了!</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220723135707.png" alt="image-20220723135707436" style="zoom:80%;" /></p></summary>
<category term="计算机组成原理" scheme="http://lapras.xyz/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="硬件" scheme="http://lapras.xyz/tags/%E7%A1%AC%E4%BB%B6/"/>
</entry>
<entry>
<title>提高爬虫效率</title>
<link href="http://lapras.xyz/2022/07/10/ee385c19.html"/>
<id>http://lapras.xyz/2022/07/10/ee385c19.html</id>
<published>2022-07-10T12:54:00.000Z</published>
<updated>2022-09-01T03:27:08.296Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>爬都可以爬</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/xigua.png" alt="99586772_p0" style="zoom: 50%;" / loading="lazy"></p><span id="more"></span><h2 id="线程进程">线程?进程?</h2><p>简单地来说,<strong>进程</strong> 是 <strong>系统进行资源调度和分配的的基本单位</strong>,是 <strong>资源单位</strong>。比如任务管理器里面管理的就是一堆进程。而 <strong>线程</strong> 则是进程的子任务,<strong>是 CPU 调度和分派的基本单位</strong>,是 <strong>执行单位</strong>。显然一个进程可以有多个线程。</p><h3 id="线程与进程的小栗子">线程与进程的小栗子</h3><p><code>Python</code> 中最简单的一个多线程例子:</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> threading <span class="token keyword">import</span> Thread<span class="token keyword">def</span> <span class="token function">func</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"Hello new World"</span><span class="token punctuation">)</span><span class="token keyword">def</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> t <span class="token operator">=</span> Thread<span class="token punctuation">(</span>target<span class="token operator">=</span>func<span class="token punctuation">)</span> <span class="token comment"># 创建新线程并安排任务</span> t<span class="token punctuation">.</span>start<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># 标记线程为可以启动状态,但具体启动时间由系统决定</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"Main thread"</span><span class="token punctuation">)</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span> main<span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre><p>或者也可以像 <code>Java</code> 中常做的那样,重写一下 <code>run</code> 方法</p><p><code>Python</code> 中最简单的一个多线程例子:</p><pre class="language-none"><code class="language-none">from multiprocessing import Processdef func(): for i in range(500): print("Hello new World")def main(): p = Process(target=func) p.start() for i in range(500): print("Main process")if __name__ == '__main__': main()</code></pre><ul><li>不难发现,API 长得几乎一样</li><li>如果想要对函数传参的话,需要使用 <code>p = Process(target=func,args=(tuple))</code> 的形式,线程同理</li></ul><h3 id="线程池和进程池">线程池和进程池</h3><p>一次性开辟若干个线程(进程),用户只需要给线程池(进程池)提交任务即可。对于具体的线程调度不需要关心</p><p><code>Python</code> 的线程池</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> concurrent<span class="token punctuation">.</span>futures <span class="token keyword">import</span> ThreadPoolExecutor<span class="token punctuation">,</span> ProcessPoolExecutor<span class="token keyword">def</span> <span class="token function">fn</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span> <span class="token comment"># 创建线程池</span> <span class="token keyword">with</span> ThreadPoolExecutor<span class="token punctuation">(</span>max_workers<span class="token operator">=</span><span class="token number">10</span><span class="token punctuation">)</span> <span class="token keyword">as</span> t<span class="token punctuation">:</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">:</span> t<span class="token punctuation">.</span>submit<span class="token punctuation">(</span>fn<span class="token punctuation">,</span> <span class="token string">"Thread-"</span> <span class="token operator">+</span> <span class="token builtin">str</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment"># 等待所有线程结束,才继续执行守护</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"ThreadPoolExecutor done"</span><span class="token punctuation">)</span></code></pre><ul><li>进程池的话,就换成 <code>ProcessPoolExecutor</code> 即可</li></ul><h3 id="使用线程池进行爬取">使用线程池进行爬取</h3><p>对 <a href="http://xinfadi.com.cn/priceDetail.html">北京新发地的菜价</a> 进行一个爬取。首先要分析一下网页,发现他的数据来源是通过更改 <code>id="current"</code> 标签的 <code>value</code> 值实现的,然后使用网络工具抓包,发现其数据是请求另一个 URL 后进行渲染的。所以我们请求数据页后将数据记录于 csv 文件中。</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> requests<span class="token keyword">import</span> csv<span class="token keyword">import</span> timef <span class="token operator">=</span> <span class="token builtin">open</span><span class="token punctuation">(</span><span class="token string">'xinfadi.csv'</span><span class="token punctuation">,</span> mode<span class="token operator">=</span><span class="token string">'w'</span><span class="token punctuation">,</span> encoding<span class="token operator">=</span><span class="token string">'utf-8'</span><span class="token punctuation">)</span>csvw <span class="token operator">=</span> csv<span class="token punctuation">.</span>writer<span class="token punctuation">(</span>f<span class="token punctuation">)</span><span class="token keyword">def</span> <span class="token function">get_one_page</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">:</span> proxy <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">'http'</span><span class="token punctuation">:</span> <span class="token string">'http://127.0.0.1:7890'</span><span class="token punctuation">,</span> <span class="token string">'https'</span><span class="token punctuation">:</span> <span class="token string">'http://127.0.0.1:7890'</span><span class="token punctuation">}</span> url <span class="token operator">=</span> <span class="token string">"http://xinfadi.com.cn/getPriceData.html"</span> headers <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'User-Agent'</span><span class="token punctuation">:</span> <span class="token string">'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'</span><span class="token punctuation">,</span> <span class="token string">'referer'</span><span class="token punctuation">:</span> <span class="token string">"http://xinfadi.com.cn/priceDetail.html"</span><span class="token punctuation">}</span> data <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'current'</span><span class="token punctuation">:</span> value<span class="token punctuation">}</span> resp <span class="token operator">=</span> requests<span class="token punctuation">.</span>post<span class="token punctuation">(</span>url<span class="token punctuation">,</span> proxies<span class="token operator">=</span>proxy<span class="token punctuation">,</span> headers<span class="token operator">=</span>headers<span class="token punctuation">,</span> data<span class="token operator">=</span>data<span class="token punctuation">)</span> resp<span class="token punctuation">.</span>encoding <span class="token operator">=</span> <span class="token string">'utf-8'</span> res <span class="token operator">=</span> resp<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token string">'list'</span><span class="token punctuation">]</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> res<span class="token punctuation">:</span> <span class="token comment"># 写入csv</span> csvw<span class="token punctuation">.</span>writerow<span class="token punctuation">(</span>i<span class="token punctuation">.</span>values<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'the </span><span class="token interpolation"><span class="token punctuation">{</span>value<span class="token punctuation">}</span></span><span class="token string"> page done'</span></span><span class="token punctuation">)</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span> <span class="token comment"># 计算时间</span> start <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">:</span> get_one_page<span class="token punctuation">(</span>i<span class="token punctuation">)</span> end <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'总共用时</span><span class="token interpolation"><span class="token punctuation">{</span>end <span class="token operator">-</span> start<span class="token punctuation">}</span></span><span class="token string">'</span></span><span class="token punctuation">)</span></code></pre><p>试着爬取 100 页的数据,发现耗时为 68.33s,而数据库有 16000 页。这显然太慢了。所以使用线程池的方式进行一个改写。</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> concurrent<span class="token punctuation">.</span>futures <span class="token keyword">import</span> ThreadPoolExecutor<span class="token punctuation">,</span> ProcessPoolExecutor<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span> <span class="token comment"># 计算时间</span> start <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment"># 使用线程池</span> <span class="token keyword">with</span> ThreadPoolExecutor<span class="token punctuation">(</span>max_workers<span class="token operator">=</span><span class="token number">50</span><span class="token punctuation">)</span> <span class="token keyword">as</span> t<span class="token punctuation">:</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">:</span> t<span class="token punctuation">.</span>submit<span class="token punctuation">(</span>get_one_page<span class="token punctuation">,</span> i<span class="token punctuation">)</span> end <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'总共用时</span><span class="token interpolation"><span class="token punctuation">{</span>end <span class="token operator">-</span> start<span class="token punctuation">}</span></span><span class="token string">'</span></span><span class="token punctuation">)</span></code></pre><p>使用了 50 个线程的表现为 12s,显然有质的提升</p><h2 id="协程">协程?</h2><p>众所周知,当程序处于 I/O 操作时,线程往往会处于堵塞状态。比如用 requests 发请求,或者读写数据库。如果我们能在堵塞状态时也能执行别的事情,那么效率就会提高。协程就是可以当线程堵塞时,选择性地切换到别的任务,提高 CPU 的利用率。所以即使在 <strong>单线程</strong> 的条件下,我们也能看到多个任务 "同时" 进行的现象。</p><blockquote><p>协程,英文叫作 coroutine,又称微线程、纤程,它是一种用户态的轻量级线程。</p><p>协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态,即所有局部状态的一个特定组合,每次过程重入时,就相当于进入上一次调用的状态。</p><p>协程本质上是个单进程,它相对于多进程来说,无须线程上下文切换的开销,无须原子操作锁定及同步的开销,编程模型也非常简单。</p><p>我们可以使用协程来实现异步操作,比如在网络爬虫场景下,我们发出一个请求之后,需要等待一定时间才能得到响应,但其实在这个等待过程中,程序可以干许多其他事情,等到响应得到之后才切换回来继续处理,这样可以充分利用 CPU 和其他资源,这就是协程的优势。</p></blockquote><h3 id="多任务异步协程">多任务异步协程</h3><p>在<code>Python</code>中实现协程,我们需要用到 <code>asyncio</code>这个库。下面先介绍一下这个库的一些基本概念</p><ul><li><code>event_loop</code>:事件循环,相当于一个无限循环,我们可以把一些函数<strong>注册</strong>到这个事件循环上,当满足条件发生的时候,就会调用对应的处理方法</li><li><code>coroutine</code>:中文翻译叫协程,在 Python 中常指代<strong>协程对象类型</strong>,我们可以将协程对象注册到时间循环中,它会被事件循环调用。我们可以使用 <code>async</code> 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象</li><li><code>task</code>:任务,它是对协程对象的进一步封装,包含了任务的各个状态,比如 <code>running</code>、<code>finished</code> 等,我们可以用这些状态来获取协程对象的执行情况</li><li><code>future</code>:代表将来执行或没有执行的任务的结果,实际上和 <code>task</code> 没有本质区别</li><li>另外,我们还需要了解 <code>async</code>/<code>await</code> 关键字,它是从 Python 3.5 才出现的,专门用于定义协程。其中,<code>async</code> 定义一个协程,<code>await</code> 用来挂起阻塞方法的执行</li></ul><p>下面举个经典例子来说明协程的含金量</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> asyncio<span class="token keyword">import</span> time<span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">func1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'func1'</span><span class="token punctuation">)</span> <span class="token keyword">await</span> asyncio<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'func1'</span><span class="token punctuation">)</span><span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">func2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'func2'</span><span class="token punctuation">)</span> <span class="token keyword">await</span> asyncio<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'func2'</span><span class="token punctuation">)</span><span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">func3</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'func3'</span><span class="token punctuation">)</span> <span class="token keyword">await</span> asyncio<span class="token punctuation">.</span>sleep<span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'func3'</span><span class="token punctuation">)</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span> start <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> loop <span class="token operator">=</span> asyncio<span class="token punctuation">.</span>get_event_loop<span class="token punctuation">(</span><span class="token punctuation">)</span> tasks <span class="token operator">=</span> <span class="token punctuation">[</span>func1<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> func2<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> func3<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span> loop<span class="token punctuation">.</span>run_until_complete<span class="token punctuation">(</span>asyncio<span class="token punctuation">.</span>wait<span class="token punctuation">(</span>tasks<span class="token punctuation">)</span><span class="token punctuation">)</span> loop<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span> end <span class="token operator">=</span> time<span class="token punctuation">.</span>time<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f'总共用时</span><span class="token interpolation"><span class="token punctuation">{</span>end <span class="token operator">-</span> start<span class="token punctuation">}</span></span><span class="token string">'</span></span><span class="token punctuation">)</span></code></pre><ol type="1"><li>将任务声明为协程对象,即使用 <code>async def</code> 的方式定义函数。任何调用该函数的行为返回的都是一个 <code>coroutine object</code>,而非运行其中的代码。如果想要运行其中的函数,则需要满足进入 <code>async</code> 模式且 <code>coroutine</code> 变为 <code>task</code></li><li>正常的 Python 代码都是 <code>sync</code> 也就是同步模式,想要切换到 <code>async</code> 模式。我们通常使用 <code>asyncio.run()</code>(Python >= 3.7),其参数为一个 <code>coroutine</code>,它会自动建立 <code>event loop</code> 并把参数 <code>coroutine</code> 变为其中的第一个 <code>task</code> 开始运行</li><li>能被 <code>await</code> 的对象有 3 种:<code>coroutine</code>、<code>task</code> 和 <code>future</code></li><li>当你 <code>await coroutine</code> 时, <span class="math inline">\(Don't \ do\ that\)</span> 。直接这样做就与同步别无二致。</li><li>所以我们需要直接 <code>await task</code>,<code>event loop</code> 就直接给出控制权,并在结束时记录返回值。这也就需要我们提前使用 <code>task = asyncio.create_task(coroutine obj)</code> 注册一个 <code>task</code></li><li>除了上述的<code>asyncio.run()</code>的方式,通常的流程应该如下<ol type="1"><li>使用<code>loop = asyncio.get_event_loop()</code>注册事件循环</li><li>使用<code>task = asyncio.create_task(coroutine obj)</code>注册任务</li><li>使用<code>loop.run_until_complete(task)</code>开始执行</li></ol></li><li>那么如果有很多 <code>task</code> 需要注册呢?<ol type="1"><li>如案例中的<code>asyncio.wait()</code>,其返回值是一个元组,包括两个集合,分别表示已完成和未完成的任务。wait第二个参数为一个超时值,达到这个超时时间后,未完成的任务状态变为pending</li><li>或者可以用 <code>asyncio.gather()</code>。其会返回一个 <code>future</code>,参数为若干个可 <code>await</code> 的对象的<code>list</code>。<code>task</code> 会被注册到 <code>event loop</code> 中,如果是 <code>coroutine</code> 则首先会被包装成 <code>task</code> 再注册到 <code>event loop</code> 中。然后返回的 <code>future</code> 其目的是告知 <code>event loop</code> 需要完成其中所有的 <code>task</code> 后才能继续执行。最后返回其中所有 <code>task</code> 的 <code>return</code> 值按照顺序返回到一个 <code>list</code> 中。其与<code>wait()</code>的主要区别就在于其任务无法取消,返回值是按照传入参数的顺序返回的结果列表</li></ol></li></ol><h4 id="绑定回调">绑定回调</h4><p>另外,我们也可以为某个 <code>task</code> 绑定一个回调方法</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> asyncio<span class="token keyword">import</span> requests<span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">request</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> url <span class="token operator">=</span> <span class="token string">'https://www.baidu.com'</span> status <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>url<span class="token punctuation">)</span> <span class="token keyword">return</span> status<span class="token keyword">def</span> <span class="token function">callback</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'Status:'</span><span class="token punctuation">,</span> task<span class="token punctuation">.</span>result<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>coroutine <span class="token operator">=</span> request<span class="token punctuation">(</span><span class="token punctuation">)</span>task <span class="token operator">=</span> asyncio<span class="token punctuation">.</span>ensure_future<span class="token punctuation">(</span>coroutine<span class="token punctuation">)</span>task<span class="token punctuation">.</span>add_done_callback<span class="token punctuation">(</span>callback<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'Task:'</span><span class="token punctuation">,</span> task<span class="token punctuation">)</span>loop <span class="token operator">=</span> asyncio<span class="token punctuation">.</span>get_event_loop<span class="token punctuation">(</span><span class="token punctuation">)</span>loop<span class="token punctuation">.</span>run_until_complete<span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'Task:'</span><span class="token punctuation">,</span> task<span class="token punctuation">)</span></code></pre><blockquote><p>这里我们定义了一个 <code>request</code> 方法,请求了百度,获取其状态码,但是这个方法里面我们没有任何 <code>print</code> 语句。</p><p>随后我们定义了一个 <code>callback</code> 方法,这个方法接收一个参数,是 <code>task</code> 对象,然后调用 <code>print</code> 方法打印了 <code>task</code> 对象的结果。这样我们就定义好了一个 <code>coroutine</code> 对象和一个回调方法。我们现在希望的效果是,当 <code>coroutine</code> 对象执行完毕之后,就去执行声明的 <code>callback</code> 方法。</p><p>那么它们两者怎样关联起来呢?很简单,只需要调用 <code>add_done_callback</code> 方法即可。我们将 <code>callback</code> 方法传递给封装好的 <code>task</code> 对象,这样当 <code>task</code> 执行完毕之后,就可以调用 <code>callback</code> 方法了。同时 <code>task</code> 对象还会作为参数传递给 <code>callback</code> 方法,调用 <code>task</code> 对象的 <code>result</code> 方法就可以获取返回结果了。</p></blockquote><h3 id="aiohttp">aiohttp</h3><p>我们仅仅将涉及 IO 操作的代码封装到 <code>async</code> 修饰的方法里面是不可行的,必须使用支持异步操作的请求方式才可以实现真正的异步,所以需要使用 <code>aiohttp</code>,下面是稍加改动后的官网的例子</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> aiohttp<span class="token keyword">import</span> asyncio<span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">fetch</span><span class="token punctuation">(</span>session<span class="token punctuation">,</span> url<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">with</span> session<span class="token punctuation">.</span>get<span class="token punctuation">(</span>url<span class="token punctuation">)</span> <span class="token keyword">as</span> response<span class="token punctuation">:</span> <span class="token keyword">return</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span>text<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> response<span class="token punctuation">.</span>status<span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token keyword">with</span> aiohttp<span class="token punctuation">.</span>ClientSession<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> session<span class="token punctuation">:</span> text<span class="token punctuation">,</span> status_code <span class="token operator">=</span> <span class="token keyword">await</span> fetch<span class="token punctuation">(</span>session<span class="token punctuation">,</span> <span class="token string">"http://httpbin.org/get"</span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"text:</span><span class="token interpolation"><span class="token punctuation">{</span>text<span class="token punctuation">[</span><span class="token punctuation">:</span><span class="token format-spec">100]</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string-interpolation"><span class="token string">f"code:</span><span class="token interpolation"><span class="token punctuation">{</span>status_code<span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">'__main__'</span><span class="token punctuation">:</span> asyncio<span class="token punctuation">.</span>run<span class="token punctuation">(</span>main<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre><p>大体上与 <code>request.session</code> 的操作方式相同。首先用 <code>async with aiohttp.ClientSession() as session:</code> 注册一个支持异步的上下文客户端会话管理器 <code>session</code>,用 <code>session</code> 去异步地发送请求 <code>async with session.get(url) as response:</code> 此时可以添加 <code>params,headers,data,cookies</code>。最后使用 <code>await resp.json() or .text() or .read()</code> 来得到数据</p><p>下面给出一个常用框架:</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">import</span> aiohttp<span class="token keyword">import</span> asyncio<span class="token keyword">def</span> <span class="token function">get_tasks</span><span class="token punctuation">(</span>session<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" 获取任务列表 :param 客户端会话 :return: 任务列表 """</span> tasks <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token keyword">for</span> i <span class="token keyword">in</span> <span class="token builtin">range</span><span class="token punctuation">(</span><span class="token number">30</span><span class="token punctuation">)</span><span class="token punctuation">:</span> tasks<span class="token punctuation">.</span>append<span class="token punctuation">(</span>fetch<span class="token punctuation">(</span>session<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> tasks<span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">fetch</span><span class="token punctuation">(</span>session<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">""" 异步获取网页的具体过程 :param 客户端会话 :return: 网页的数据 """</span> <span class="token keyword">async</span> <span class="token keyword">with</span> session<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'http://httpbin.org/get'</span><span class="token punctuation">)</span> <span class="token keyword">as</span> resp<span class="token punctuation">:</span> <span class="token comment"># 断言,如果状态码不是 200,则抛出异常</span> <span class="token keyword">assert</span> resp<span class="token punctuation">.</span>status <span class="token operator">==</span> <span class="token number">200</span> <span class="token keyword">return</span> <span class="token keyword">await</span> resp<span class="token punctuation">.</span>json<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token keyword">async</span> <span class="token keyword">def</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> results <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token keyword">async</span> <span class="token keyword">with</span> aiohttp<span class="token punctuation">.</span>ClientSession<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">as</span> session<span class="token punctuation">:</span> tasks <span class="token operator">=</span> get_tasks<span class="token punctuation">(</span>session<span class="token punctuation">)</span> responses <span class="token operator">=</span> <span class="token keyword">await</span> asyncio<span class="token punctuation">.</span>gather<span class="token punctuation">(</span><span class="token operator">*</span>tasks<span class="token punctuation">)</span> <span class="token keyword">print</span><span class="token punctuation">(</span>responses<span class="token punctuation">)</span>asyncio<span class="token punctuation">.</span>run<span class="token punctuation">(</span>main<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre><h2 id="参考">参考</h2><p>https://www.kingname.info/2020/03/23/insert-sprit/</p><p>https://www.bilibili.com/video/BV1oa411b7c9</p><p>https://cuiqingcai.com/202271.html</p>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>爬都可以爬</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/xigua.png" alt="99586772_p0" style="zoom: 50%;" /></p></summary>
<category term="爬虫学习" scheme="http://lapras.xyz/categories/%E7%88%AC%E8%99%AB%E5%AD%A6%E4%B9%A0/"/>
<category term="爬虫" scheme="http://lapras.xyz/tags/%E7%88%AC%E8%99%AB/"/>
<category term="Python" scheme="http://lapras.xyz/tags/Python/"/>
</entry>
<entry>
<title>Python 网页解析库小介绍</title>
<link href="http://lapras.xyz/2022/07/09/f8dd4268.html"/>
<id>http://lapras.xyz/2022/07/09/f8dd4268.html</id>
<published>2022-07-09T02:57:00.000Z</published>
<updated>2022-08-21T07:21:56.320Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>在用 <code>Python</code> 进行网络爬虫,拿到 HTML 内容之后势必要对其进行一些内容上的解析。之前用过正则表达式 <code>re</code> 和 <code>BeautifulSoup</code>。前者速度挺快的,但是代码可读性较差。后者虽然简单,但是速度令人捉急。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220709111111.jpg" alt="92274729_p0_master1200" style="zoom:80%;" / loading="lazy"></p><span id="more"></span><p>先放个速度对比图,数据来源 <a href="https://zhuanlan.zhihu.com/p/25887452">知乎-拒绝撕逼,用数据来告诉你选择器到底哪家强</a></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220726005550.png" alt="img" style="zoom:80%;" / loading="lazy"></p><h2 id="xpath">XPath</h2><p><code>XPath</code> 是一门在 <code>XML</code> ⽂档中查找信息的语言.<code>XPath</code> 可用来在 <code>XML</code> 文档中对元素和属性进行遍历。而我们熟知的 <code>HTML</code> 恰巧属于 <code>XML</code>。 所以完全可以用其进行解析。</p><h3 id="xml-基本知识">XML 基本知识</h3><pre class="language-markup" data-language="markup"><code class="language-markup"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>book</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>高等数学<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>price</span><span class="token punctuation">></span></span>28.0<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>price</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>author</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>nick</span><span class="token punctuation">></span></span>武钟祥<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>nick</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>nick</span><span class="token punctuation">></span></span>张宇<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>nick</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>author</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>book</span><span class="token punctuation">></span></span></code></pre><ul><li>DOM 将 HTML 表示为标签的树形结构。</li><li>每一对标签都是一个节点</li><li><strong>标签中的属性及文本也可视为该节点的子节点</strong></li><li>节点之间有父子关系,同胞关系。以及先辈和后裔这种一代及以上的关系</li><li>利用缩进,可以很好的理解这些概念</li></ul><h3 id="安装导入">安装导入</h3><pre class="language-bash" data-language="bash"><code class="language-bash">pip <span class="token function">install</span> lxml -i https://pypi.tuna.tsinghua.edu.cn/simple</code></pre><p>有很多库都提供了 Xpath 解析的方法,这里选择 <code>lxml</code>。</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> lxml <span class="token keyword">import</span> etree<span class="token comment"># 或者</span><span class="token keyword">from</span> lxml <span class="token keyword">import</span> htmletree <span class="token operator">=</span> html<span class="token punctuation">.</span>etree</code></pre><h3 id="解析过程">解析过程</h3><ol type="1"><li>准备源文件</li><li>得到解析对象 <code>et = etree.HTML(html)</code></li><li>使用 <code>xpath</code> 方法进行解析,视情况选择直接解析或者进一步 <code>for</code> 循环解析。</li></ol><h3 id="具体方法介绍">具体方法介绍</h3><ol type="1"><li><p>节点选取</p><ul><li>node:选取此节点的所有子节点</li><li><code>/</code>:从根节点选取</li><li><code>//</code>:从当前节点选择文档中后裔节点</li><li><code>.</code>:选取当前节点</li><li><code>..</code>:选取当前节点的父节点</li><li><code>@</code>:选取属性</li></ul></li><li><p>节点选取举例</p><table><thead><tr class="header"><th>表达式</th><th>描述</th></tr></thead><tbody><tr class="odd"><td>bookstore</td><td>选取 bookstore 元素的所有子节点</td></tr><tr class="even"><td>/ bookstore</td><td>选取根元素 bookstore ,相当于绝对路径的写法</td></tr><tr class="odd"><td>bookstore/book</td><td>选取属于 bookstore 的子元素的所有 book 元素</td></tr><tr class="even"><td>//book</td><td>选取所有 book 子元素,而不管它们在文档中的位置</td></tr><tr class="odd"><td>bookstore//book</td><td>选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置</td></tr><tr class="even"><td>//<span class="citation" data-cites="lang">@lang</span></td><td>选取名为 lang 的所有属性</td></tr></tbody></table></li><li><p>值筛选</p><p>上述表达式都会返回一个 <code>Element</code> 类的列表,我们可以在 <strong>表达式中</strong> 使用 <code>[]</code> 进行进一步筛选节点。</p><p>用法为在任意节点后添加 <code>[]</code>,里面的表达式可以为:</p><ul><li><code>1</code>:选取该节点的第一个元素</li><li><code>last()</code>:选取该节点的最后一个元素</li><li><code>position()</code>:选取位置符合布尔表达式的元素。比如 <code>position()>4</code>。</li><li><code>@lang</code>:选取拥有名为 lang 的属性的该节点元素。比如 <code>//title[@lang]</code> 表示选取所有拥有名为 lang 的属性的 title 元素</li><li><code>@lang='xx'</code>:选取拥有名为 lang 的属性且值为 xx 的该节点元素</li><li><code>contains(@属性,"值")</code>:选取属性包含有某个值的节点元素</li><li>可以搭配 <code>and or |</code> 使用</li></ul></li><li><p>通配符</p><ul><li><code>*</code>:匹配任何元素节点</li><li><code>@*</code>:匹配任何属性节点</li><li><code>node()</code>:匹配任何节点</li></ul></li><li><p>获取数据</p><ul><li><code>/text()</code>:获取节点文本内容</li><li><code>/@属性</code>:获取节点某个属性的内容</li></ul></li></ol><h3 id="浏览器工具">浏览器工具</h3><ol type="1"><li><p>首先当然是万能的 <code>F12</code>。可以通过右键元素选择检查,找到元素的相对位置</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220708154611.png" alt="F12 检查" / loading="lazy"><figcaption aria-hidden="true">F12 检查</figcaption></figure><p>同时,在开发者工具的 <code>Element</code> 中搜索时可以使用 <code>XPath</code> 表达式进行检索。也可以右键元素,选择复制 <code>XPath</code></p></li><li><p><code>selectorshub</code> 这个浏览器插件,可以直接生成 xpath, cssSelector, Playwright selectors , jQuery, JS Path 等路径</p></li></ol><h3 id="小栗子">小栗子</h3><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> lxml <span class="token keyword">import</span> etree<span class="token keyword">import</span> requests<span class="token comment"># 爬取B站排行榜</span>url <span class="token operator">=</span> <span class="token string">'https://www.bilibili.com/v/popular/rank/all'</span>headers <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'User-Agent'</span><span class="token punctuation">:</span> <span class="token string">'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'</span><span class="token punctuation">}</span>proxy <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">'http'</span><span class="token punctuation">:</span> <span class="token string">'http://127.0.0.1:7890'</span><span class="token punctuation">,</span> <span class="token string">'https'</span><span class="token punctuation">:</span> <span class="token string">'http://127.0.0.1:7890'</span><span class="token punctuation">}</span>resp <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>url<span class="token punctuation">,</span> headers<span class="token operator">=</span>headers<span class="token punctuation">,</span> proxies<span class="token operator">=</span>proxy<span class="token punctuation">)</span>resp<span class="token punctuation">.</span>encoding <span class="token operator">=</span> <span class="token string">'utf-8'</span><span class="token comment"># etree把网页内容转换成可以操作的对象</span>et <span class="token operator">=</span> etree<span class="token punctuation">.</span>HTML<span class="token punctuation">(</span>resp<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span class="token comment"># 获取排行榜的标题</span>titles <span class="token operator">=</span> et<span class="token punctuation">.</span>xpath<span class="token punctuation">(</span><span class="token string">"//div[@class='info']/a/text()"</span><span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span>titles<span class="token punctuation">)</span></code></pre><h2 id="pyquery">Pyquery</h2><p>与 JS 中使用的 Jquery 基本相同,都是使用 CSS 选择器达到解析的目的。</p><h3 id="常用的-css-选择器">常用的 CSS 选择器</h3><ol type="1"><li>标签选择器 <code>Label</code>:选择器名和指定的 HTML 元素名的不区分大小写的匹配。这是选择所有指定类型的最简单方式</li><li>类选择器 <code>.Label</code>:类名是在 HTML class 文档元素属性中没有空格的任何值。类 Class 是可以重复且有多个的</li><li>ID 选择器 <code>#Label</code>: 任何元素都可以使用 id 属性设置唯一的 ID 名称。 这是选择单个元素的最有效的方式</li><li>通配选择器 <code>*</code>:选择在一个页面中的所有元素, 常常搭配使用</li><li>组合器分组 <code>Label1, Label2......</code>:选择所有出现的 Label</li><li>后代选择器 <code>Label1 Label2</code>:选择 <code>Label1</code> 中的所有 <code>Label2</code> 后裔</li><li>子选择器 <code>Label1 > Label2</code>:选择 <code>Label1</code> 中的所有 <code>Label2</code> 直接后代</li><li>相邻兄弟选择器 <code>Label1+Label2</code>:选择 <code>Label2</code> 元素,它是 <code>Label1</code> 的下一个直接兄弟元素</li><li>通用兄弟选择器 <code>Label1~Label2</code>:选择 <code>Label2</code> 元素,它是 <code>Label1</code> 的兄弟元素</li><li>属性选择器 <code>Label[attr]</code> 表示选择包含 attr 属性的所有元素,<code>Label[attr]=val</code> 表示仅选择 attr 属性被赋值为 val 的所有元素,<code>[attr~=val]</code>:该选择器仅选择 attr 属性的值(以空格间隔出多个值)中有包含 val 值的所有元素</li><li>伪类选择器:内容繁多,功能丰富。详见 <a href="https://www.w3school.com.cn/css/css_pseudo_classes.asp">w3c</a></li></ol><h3 id="安装导入-1">安装导入</h3><pre class="language-bash" data-language="bash"><code class="language-bash">pip <span class="token function">install</span> pyquery -i https://pypi.tuna.tsinghua.edu.cn/simple</code></pre><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> pyquery <span class="token keyword">import</span> PyQuery <span class="token keyword">as</span> pq</code></pre><h3 id="解析过程-1">解析过程</h3><ol type="1"><li>准备源文件</li><li>得到解析对象 <code>doc = pq(resp.text)</code></li><li>使用 <code>doc(CSS选择器表达式)</code> 的方式获取 <code>HTML</code> 内容</li><li>使用 <code>.text()</code> 获取文本,使用 <code>.attr('属性')</code> 获取属性值</li><li>使用 <code>.attr('属性', '值')</code> 来修改属性或者添加属性</li><li>使用 <code>.children(css)</code> 查找子节点,<code>.find(css)</code> 查找子孙节点</li><li>使用 <code>.parent(css)</code> 查找父节点,<code>.parents(css)</code> 查找祖先节点</li><li>使用 <code>.siblings(css)</code> 查找兄弟节点</li><li>如果选择的内容超过一条后想要获取他们的文本属性值,则需使用 <code>.items()</code>,返回一个迭代器后使用 <code>for</code> 迭代或者使用 <code>list comprehension</code></li></ol><h3 id="小例子">小例子</h3><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> pyquery <span class="token keyword">import</span> PyQuery <span class="token keyword">as</span> pq<span class="token keyword">import</span> requests<span class="token comment"># 爬取B站排行榜</span>url <span class="token operator">=</span> <span class="token string">'https://www.bilibili.com/v/popular/rank/all'</span>headers <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'User-Agent'</span><span class="token punctuation">:</span> <span class="token string">'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'</span><span class="token punctuation">}</span>proxy <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">'http'</span><span class="token punctuation">:</span> <span class="token string">'http://127.0.0.1:7890'</span><span class="token punctuation">,</span> <span class="token string">'https'</span><span class="token punctuation">:</span> <span class="token string">'http://127.0.0.1:7890'</span><span class="token punctuation">}</span>resp <span class="token operator">=</span> requests<span class="token punctuation">.</span>get<span class="token punctuation">(</span>url<span class="token punctuation">,</span> headers<span class="token operator">=</span>headers<span class="token punctuation">,</span> proxies<span class="token operator">=</span>proxy<span class="token punctuation">)</span>resp<span class="token punctuation">.</span>encoding <span class="token operator">=</span> <span class="token string">'utf-8'</span><span class="token comment"># pyquery把网页内容转换成可以操作的对象</span>p <span class="token operator">=</span> pq<span class="token punctuation">(</span>resp<span class="token punctuation">.</span>text<span class="token punctuation">)</span><span class="token comment"># 获取排行榜的标题</span>items <span class="token operator">=</span> p<span class="token punctuation">(</span><span class="token string">'.rank-list .info>a'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>items<span class="token punctuation">(</span><span class="token punctuation">)</span>title <span class="token operator">=</span> <span class="token punctuation">[</span>item<span class="token punctuation">.</span>text<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">for</span> item <span class="token keyword">in</span> items<span class="token punctuation">]</span><span class="token keyword">print</span><span class="token punctuation">(</span>title<span class="token punctuation">)</span></code></pre><h2 id="parsel">Parsel</h2><p>Parsel 这个库可以解析 HTML 和 XML,同时支持 CSS 和 XPath 两种解析方式并融合了正则表达式的提取功能。<a href="https://scrapy.org/">scrapy</a> 选择器部分也是基于此二次封装的产物。</p><h3 id="安装导入-2">安装导入</h3><pre class="language-bash" data-language="bash"><code class="language-bash">pip <span class="token function">install</span> parsel -i https://pypi.tuna.tsinghua.edu.cn/simple</code></pre><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> parsel <span class="token keyword">import</span> Selector</code></pre><h3 id="解析过程-2">解析过程</h3><ol type="1"><li><p>首先创建一个 <code>Selector</code> 对象,传入 HTML 字符串。</p><pre class="language-python" data-language="python"><code class="language-python">selector <span class="token operator">=</span> Selector<span class="token punctuation">(</span>text <span class="token operator">=</span> HTML<span class="token punctuation">)</span></code></pre></li><li><p>使用 <code>.css()</code> 或者 <code>.xpath()</code> 进行解析,并通过 CSS 中的 <code>::text</code> 或者 <code>::attr(属性)</code>,通过 <code>XPath</code> 的 <code>/text()</code> 和 <code>/@属性</code> 获取内容,返回一个 <code>SelectorList</code> 迭代对象</p></li><li><p><code>SelectorList</code> 进行遍历用 <code>.get()</code> 获取内容文本,或者 <code>.getall()</code> 返回内容文本列表</p></li><li><p><code>SelectorList</code> 使用 <code>.re()</code> 可以使用正则表达式进一步提取内容并返回列表</p></li></ol>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>在用 <code>Python</code> 进行网络爬虫,拿到 HTML 内容之后势必要对其进行一些内容上的解析。之前用过正则表达式 <code>re</code> 和 <code>BeautifulSoup</code>。前者速度挺快的,但是代码可读性较差。后者虽然简单,但是速度令人捉急。</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220709111111.jpg" alt="92274729_p0_master1200" style="zoom:80%;" /></p></summary>
<category term="爬虫学习" scheme="http://lapras.xyz/categories/%E7%88%AC%E8%99%AB%E5%AD%A6%E4%B9%A0/"/>
<category term="爬虫" scheme="http://lapras.xyz/tags/%E7%88%AC%E8%99%AB/"/>
<category term="Python" scheme="http://lapras.xyz/tags/Python/"/>
</entry>
<entry>
<title>计算机组成原理笔记(三)</title>
<link href="http://lapras.xyz/2022/06/30/26b9f9fa.html"/>
<id>http://lapras.xyz/2022/06/30/26b9f9fa.html</id>
<published>2022-06-30T14:58:07.000Z</published>
<updated>2022-08-21T07:21:56.350Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>市原大輔走了,但没完全走。崛起你真的好温柔。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220630231342.jpg" alt="91953835_p0_master1200" style="zoom:80%;" / loading="lazy"></p><span id="more"></span><h2 id="存储器分类">存储器分类</h2><h3 id="按存储介质分类">按存储介质分类</h3><ol type="1"><li>半导体存储器(易丢失):TTL(集成度低,功耗高),MOS(集成度高,功耗低)</li><li>磁表面存储器:磁头(读/写)、载磁体(存储)</li><li>磁芯存储器:硬磁材料,环状元件</li><li>光盘存储器:激光(读/写),磁光材料(存储)</li></ol><h3 id="按存取方式分类">按存取方式分类</h3><ol type="1"><li><p>存取时间与物理地址无关 (随机访问)</p><ul><li><p>随机存储器(<strong>在程序执行过程中</strong> 可读可写)</p></li><li><p>只读存储器(<strong>在程序执行过程中</strong> 只读)</p></li></ul></li><li><p>存取时间与物理地址有关(串行访问)</p><ul><li><p>顺序存取存储器,如磁带</p></li><li><p>直接存取存储器,如磁盘</p></li></ul></li></ol><h3 id="按在计算机中的作用分类">按在计算机中的作用分类</h3><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%8C%89%E5%9C%A8%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%AD%E7%9A%84%E4%BD%9C%E7%94%A8%E5%88%86%E7%B1%BB.png" alt="按在计算机中的作用分类" / loading="lazy"><figcaption aria-hidden="true">按在计算机中的作用分类</figcaption></figure><ul><li><p>Cache:高速缓冲存储器,位于 CPU 和主存之间,用于缓存主存的数据。本质上是一块集成到 CPU 的 SRAM。</p></li><li><p>RAM:</p><ul><li><p>静态 RAM(SRAM)</p></li><li><p>动态 RAM(DRAM)</p></li></ul></li><li><p>ROM:</p><ul><li><p>MROM(掩膜 ROM)</p></li><li><p>PROM(可编程 ROM)</p></li><li><p>EPROM(电可编程 ROM)</p></li><li><p>EEPROM(电可编程可擦除 ROM)</p></li></ul></li><li><p>Flash Memory(闪存):可用于固态硬盘</p></li></ul><h2 id="存储器的层次结构">存储器的层次结构</h2><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%AD%98%E5%82%A8%E5%99%A8%E5%9C%A8%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%B8%AD%E7%9A%84%E4%BD%8D%E7%BD%AE.png" alt="image-20220628201956665" / loading="lazy"><figcaption aria-hidden="true">image-20220628201956665</figcaption></figure><ul><li>速度:从快到慢</li><li>容量:从小到大</li><li>价格:从高到低</li><li>寄存器:集成在 CPU 当中,I/O 端口中也存在。<ul><li>从体系结构的角度,供给给机器语言程序员使用的称为体系结构寄存器。</li></ul></li><li>缓存:一部分集成在 CPU 当中。如果 CPU 需要读取或写入数据,缓存的速度更快</li></ul><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%BC%93%E5%AD%98%20%E4%B8%BB%E5%AD%98%E5%B1%82%E6%AC%A1%E5%92%8C%E4%B8%BB%E5%AD%98%20%E8%BE%85%E5%AD%98%E5%B1%82%E6%AC%A1.png" alt="缓存 主存层次和主存 辅存层次" / loading="lazy"><figcaption aria-hidden="true">缓存 主存层次和主存 辅存层次</figcaption></figure><ul><li>缓存到辅存的管理由 CPU 提供的硬件来完成,通常软件开发者不需要了解其中的细节。</li><li>主存到辅存的管理由 CPU 提供的 <strong>MMU</strong> 以及操作系统提供的 <strong>内存管理模块</strong> 共同完成,不仅可以为每个进程提供 <strong>独立的地址空间</strong>,还可以借助辅存让那些比主存容量还要大的程序得以运行。</li></ul><h2 id="主存储器">主存储器</h2><h3 id="概述">概述</h3><ol type="1"><li>主存的基本结构</li></ol><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%BB%E5%AD%98%E7%9A%84%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84.png" alt="主存的基本结构" / loading="lazy"><figcaption aria-hidden="true">主存的基本结构</figcaption></figure><ul><li>MAR:记录了我们要访问的存储单元地址</li><li>MDR:记录了我们要读出或者写入的数据</li><li>具体的读还是写,则是由控制电路控制读写电路实现</li></ul><ol start="2" type="1"><li>主存和 CPU 联系</li></ol><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%BB%E5%AD%98%E5%92%8CCPU%E8%81%94%E7%B3%BB.png" alt="主存和 CPU 联系" / loading="lazy"><figcaption aria-hidden="true">主存和 CPU 联系</figcaption></figure><ul><li>MDR 和 MAR 都是集成在 CPU 上的,但属于主存</li><li>数据总线完成 CPU 和主存的信息传输</li><li>地址总线是单向的,CPU 给定要访问的内存单元地址</li><li>控制信号:读取或者写入</li></ul><ol start="3" type="1"><li><p>主存中存储单元地址的分配</p><p>假设当前使用的存储器字长为 32 位,要存储的数为 12345678H。首先根据 8 位一个字节,每个字节都要有一个 <strong>字地址</strong>,那么一个存储单元就可以分为 4 个字节。<strong>字地址的选择是当前字节的第一个地址</strong>,所以根据数据的存放顺序,可以分为两种方式。</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%AD%98%E5%82%A8%E5%8D%95%E5%85%83%E5%9C%B0%E5%9D%80%E5%88%86%E9%85%8D.png" alt="大端和小端方式" / loading="lazy"><figcaption aria-hidden="true">大端和小端方式</figcaption></figure><ul><li>如图,每一个格子都有八位(存储两个 16 进制数),一行就是一个存储单元(字长为 32)</li><li>字地址从 0 开始编号</li><li><strong>主存的寻址方式</strong>:按字节寻址(每个地址对应一个字节)和按字寻址(每个地址对应一个字)</li></ul></li><li><p>主存的技术指标</p><ul><li><p>存储容量:存放二进制数据的总位数或总字节数</p></li><li><p>存储速度:</p><ul><li>存取时间:存储器的访问(读/写)时间,即给出地址信号到访问完成所需的时间</li><li>存取周期:<strong>连续</strong> 两次 <strong>独立</strong> 的存储器访问操作所需的 <strong>最小</strong> 时间间隔,通常 <strong>大于</strong> 存取时间</li></ul></li><li><p>存储器的带宽:单位时间内写入的位数</p></li></ul></li></ol><h3 id="半导体芯片简述">半导体芯片简述</h3><ol type="1"><li><p>半导体芯片的存储结构</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%8D%8A%E5%AF%BC%E4%BD%93%E8%8A%AF%E7%89%87%E7%9A%84%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84.png" alt="半导体芯片的存储结构" / loading="lazy"><figcaption aria-hidden="true">半导体芯片的存储结构</figcaption></figure><ul><li>地址线:单向,由 CPU 或 I/O 设备</li><li>数据线:双向,读取向外,写入向内</li><li><strong>芯片容量</strong>:假设地址线有 <span class="math inline">\(n\)</span> 条,数据线有 <span class="math inline">\(m\)</span> 条。则芯片容量为 <span class="math inline">\(m \times 2^n\)</span> 位,通常将 <span class="math inline">\(2^n\)</span> 表示为 <span class="math inline">\(K\)</span> 的形式,即 <span class="math inline">\(2^{10}\)</span> 为 <span class="math inline">\(1K\)</span> 。然后写为 <span class="math inline">\(\alpha K \times m\)</span> 位 ,其中 <span class="math inline">\(\alpha\)</span> 为系数</li><li>片选线:传输片选信号,用来选中具体的芯片(存储器可能由多个存储芯片构成)。表示为 <span class="math inline">\(\overline{CS},\overline{CE}\)</span></li><li>读写控制线: <span class="math inline">\(\overline {WE}\)</span> (低电平写,高电平读),或者用两根线表示: <span class="math inline">\(\overline{OE},\overline{WE}\)</span></li></ul></li><li><p>半导体存储芯片扩展</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%AD%98%E5%82%A8%E8%8A%AF%E7%89%87%E6%89%A9%E5%B1%95.png" alt="存储芯片扩展" / loading="lazy"><figcaption aria-hidden="true">存储芯片扩展</figcaption></figure><p>首先是位扩展:将 8 片一位的并为一组,则得到 <span class="math inline">\(16K \times 8\)</span> 位存储器</p><p>然后是字扩展:复制为 4 组即可得到 <span class="math inline">\(64K \times 8\)</span> 位存储器</p><p>所以一共使用了 32 片存储芯片,即其存储容量的倍数关系</p></li><li><p>半导体芯片的译码驱动方式</p><p>线选法</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%BA%BF%E9%80%89%E6%B3%95.png" alt="线选法" / loading="lazy"><figcaption aria-hidden="true">线选法</figcaption></figure><p>线选法主要是通过地址译码器,走指定字线到目标的存储单元。并且根据读写控制电路选择数据的流通方向。显然这样做系统的结构非常清晰,但是内部字线的数量会随着地址增加而变得非常臃肿。</p><p>重合法</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E9%87%8D%E5%90%88%E6%B3%95.png" alt="重合法" / loading="lazy"><figcaption aria-hidden="true">重合法</figcaption></figure><p>重合法将所有的存储单元形成一个矩阵布局。通过行(X 地址)列(Y 地址)分别进行译码操作。</p></li></ol><h3 id="随机存取存储器ram">随机存取存储器(RAM)</h3><h4 id="静态-ramsram">静态 RAM(SRAM)</h4><p>SRAM 使用 <strong>触发器</strong> 来保存 0 和 1 这两个状态。具体的电路实现就省略了。下面开始举例:</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/Intel%202114.png" alt="Intel 2114" / loading="lazy"><figcaption aria-hidden="true">Intel 2114</figcaption></figure><ul><li><span class="math inline">\(\overline {WE}\)</span> :读写控制</li><li><span class="math inline">\(\overline {CS}\)</span> :片选信号,只有当其为低电平时,该芯片被选中</li><li><span class="math inline">\(A\)</span> :地址线,一共有 10 条,说明是地址位为 <span class="math inline">\(1K\)</span></li><li><span class="math inline">\(I/O\)</span> :数据线,一共有 4 条,说明数据位为 4</li></ul><p>下面举个例子,将 2114 拼成一个 64X64 的存储阵列,通过重合法,一次 <strong>读取同一行的四列数据</strong></p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/64X64%202114.png" alt="64X64 2114" / loading="lazy"><figcaption aria-hidden="true">64X64 2114</figcaption></figure><ul><li>不难发现,行地址全部正常编号。列地址只使用了 4 位,即编号 16 个地址。那么将列数 64 与 16 相除,得到四组。这样一来每次就能实现选取同一行的四列数据</li></ul><h4 id="动态-ramdram">动态 RAM(DRAM)</h4><p>DRAM 使 <strong>用电容存储电荷的方式来存储 1,0</strong>。读数据时,当电容是高电平的时候为 1,低电平的时候为 0。写数据时,写 1 就是为电容充电,写 0 则是电容放电。由于电容会通过电路漏电,时间一久电荷就会流失,导致信息丢失,因此需要周期性的刷新电容(为电容充电)。其具体电路实现分为 <strong>单管</strong> 和 <strong>三管</strong></p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220629125609.png" alt="Intel1103-DRAM 三管读取数据" / loading="lazy"><figcaption aria-hidden="true">Intel1103-DRAM 三管读取数据</figcaption></figure><ul><li>由图易得,是 <span class="math inline">\(1K \times 1\)</span> 位的存储器。通过重合法进行译码</li><li>首先是行地址,注意这里除了 5 位行地址,还包括一位读取信号。列地址同理</li><li>得到数据后,通过读写控制电路向外界传输</li></ul><h4 id="动态-ram-的刷新">动态 RAM 的刷新</h4><p>回想动态 RAM 的操作过程,首先是行地址取出一整行的数据到某条选择线上。这时我们在选择线上加装一个刷新放大器,即可保存原本的数值。所以,<strong>刷新只与行地址有关,每次刷新一行</strong></p><ol type="1"><li>集中刷新:存在死区</li><li>分散刷新:无死区,但读写周期加长,性能变差</li><li>异步刷新:存在小死区,但是如果安排得当。比如放置在 CPU 进行指令译码时,则无死区。</li></ol><h4 id="动态-ram-和静态-ram-比较">动态 RAM 和静态 RAM 比较</h4><ol type="1"><li>存储原理:输出 DRAM 使用电容存储数据;SRAM 使用触发器存储数据</li><li>集成度:DRAM 的基本单元电路结构更简单,方便大规模集成,因此 DRAM 集成度更高</li><li>引脚数量:DRAM 通常将行列地址复用引脚,因此引脚数量少(进而芯片封装的体积较小);而 SRAM 出于速度的考虑,一般不会这样做</li><li>功耗:DRAM 的功耗主要消耗在电容充放电,功耗较小;SRAM 的使用触发器存放数据,而构成触发器的管子有部分是一直导通的,也就一直消耗功率,因此功耗较大</li><li>价格:同样存储一个 bit,DRAM 的单元电路简单,价格低;SRAM 的单元电路复杂,用的管子多,因此价格高</li><li>速度:访问 DRAM 伴随着电容的充放电,因此速度较慢;而 SRAM 采用 chu'fa,访问速度快</li><li>刷新:DRAM 需要周期性的刷新其中的电容,才能保持数据;SRAM 则没有这个需要。</li></ol><h3 id="只读存储器rom">只读存储器(ROM)</h3><p>ROM 的发展历程</p><ol type="1"><li>早期的 ROM 在出厂时就会被厂家烧写好内容,且不支持用户再次对 ROM 进行编程</li><li>用户可以使用专门的擦写设备对芯片进行一次擦写,<strong>仅可擦写一次</strong></li><li>用户可以使用专门的擦写设备对芯片进行多次擦写,比如用紫外线擦写的 EPROM,这时候擦写还比较麻烦</li><li>EEPROM 的出现使得用户可以多次擦写 ROM,且无需使用专门的擦写设备</li></ol><h4 id="几种-rom-的简介">几种 ROM 的简介</h4><ul><li>MROM(掩模式只读存储器):<strong>行列选择线交叉处有 MOS 管则表示 1,无 MOS 管则表示 0。</strong> 因为有无 MOS 管在芯片生产出来后就决定了,因此不可编程。</li><li>PROM(可编程只读存储器):通常实现为在行列选择线之间 <strong>添加一根熔丝</strong>,编程时需要写 0 则加大电流让熔丝熔断,否则熔丝连通表示 1。由于熔丝熔断后不可在连接,因此 <strong>只可进行一次编程</strong>。</li><li>EPROM(可擦除可编程式只读存储器):EPROM 可通过对芯片中的 <strong>N 型沟道浮动栅 MOS 管</strong> 的 D 端加电压,形成浮动栅,进而电路不导通表示 0,反之表示 1。浮动栅可由 <strong>紫外线照射后消除</strong>,因此可以利用紫外线实现芯片的全部擦写,从而实现芯片的多次编程。</li><li>EEPROM(电子式可擦除可编程只读存储器):EEPROM 改进了芯片的擦写功能,实现了电可擦写,且既可局部擦写也可全局擦写。</li><li>Flash Memory(闪存):,较 EEPROM 成本更低,单元电路更为简单,集成度高,访问速度也更快。不过寿命不如 EEPROM,因此闪存通常用于保存大量数据,而 EEPROM 则用于存放不需要经常改变的一些系统参数(数据量少)。只有少数支持片上执行(XIP)的闪存(如 NorFlash)才可以作为主存储器,存放程序(不可以随机写,因此无法存放程序运行时加工的数据)。而大部分闪存一般用作辅存。</li></ul><h3 id="存储器的扩展">存储器的扩展</h3><h4 id="位扩展">位扩展</h4><p>电路连接时,两块或多块芯片共用地址线,数据线则分别与 CPU 的不同数据线连接,片选和读写控制连接在一起,实现同时读,同时写。</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%BD%8D%E6%89%A9%E5%B1%95.png" alt="2 片 1Kx4 组成 1Kx8" / loading="lazy"><figcaption aria-hidden="true">2 片 1Kx4 组成 1Kx8</figcaption></figure><h4 id="字扩展">字扩展</h4><p>电路连接时,共用低位的地址线,片选信号则通过译码器连接 CPU 地址线的高位,芯片数量少时,也可以不用译码器,直接使用非门</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%AD%97%E6%89%A9%E5%B1%95.png" alt="2 片 1Kx8 组成 2Kx8" / loading="lazy"><figcaption aria-hidden="true">2 片 1Kx8 组成 2Kx8</figcaption></figure><h4 id="字位扩展">字、位扩展</h4><p>先按位扩展将多个芯片组成一个位数更多的芯片组,各组之间再按照字扩展的连接方式与 CPU 连接</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/8%E7%89%871Kx4%E7%BB%84%E6%88%904Kx8.png" alt="8 片 1Kx4 组成 4Kx8" / loading="lazy"><figcaption aria-hidden="true">8 片 1Kx4 组成 4Kx8</figcaption></figure><h3 id="存储器与-cpu-的连接">存储器与 CPU 的连接</h3><ol type="1"><li>地址线的连接</li><li>数据线的连接</li><li>读/写命令线的连接</li><li>片选线的连接</li><li>合理选择存储芯片:系统配置和系统程序使用 ROM,用户程序选择 RAM。</li><li>其他 时序、负载</li></ol><h3 id="对存储器进行校验">对存储器进行校验</h3><p>复杂的电磁环境下,工作中的存储器受到干扰,可能导致其内部存储的 1、0 发生翻转,即 1 变成 0、0 变成 1,这样存储的信息就出错了。如果我们无法得知这一情况,而在程序中使用了错误的数据,那么就可能造成损失。</p><p>一个编码集合中,任意两个合法编码之间 <strong>二进制位数</strong> 的 <strong>最少差异</strong> 称为 <strong>编码的最小距离</strong>,编码的纠错和检错能力与其最小距离有关。具体关系如下: <span class="math display">\[L - 1 = D + C (D >= C)\]</span></p><ul><li>L:编码的最小距离</li><li>D:能够检错的位数</li><li>C:能够纠错的位数</li></ul><p>常用的检验方法有奇偶校验,海明码,CRC 循环校验</p><h4 id="奇偶校验">奇偶校验</h4><p>奇校验:使待校验 bit 串和校验位共有 <strong>奇数</strong> 个 1</p><p>偶校验:使待校验 bit 串和校验位共有 <strong>偶数</strong> 个 1</p><h4 id="海明码汉明码">海明码(汉明码)</h4><p>是一种多重分组奇偶校验。将数据组织为 <span class="math inline">\(k\)</span> 个分组,每组进行奇偶校验。不仅能检验是否出错,也能定位错误,但定位代价较大,假设有 <span class="math inline">\(n\)</span> 位有效码,设置了 <span class="math inline">\(k\)</span> 位校验码,则 <span class="math inline">\(n\)</span> 与 <span class="math inline">\(k\)</span> 关系为 <span class="math display">\[2^k ≥ n + k + 1\]</span> + 假设有 <span class="math inline">\(k\)</span> 个校验位,一位有 0 或 1 两种情况, <span class="math inline">\(k\)</span> 位就有 <span class="math inline">\(2^k\)</span> 种排列情况,能表示 <span class="math inline">\(2^k\)</span> 种状态。其中一个状态用来表示正确(没有错误发生)的这种情况。其余的 <span class="math inline">\(2^k-1\)</span> 种状态来表示错误发生在哪一位。总共有 <span class="math inline">\(n+k\)</span> 位,所以 <span class="math inline">\(2^k-1\)</span> 要大等于 <span class="math inline">\(n+k\)</span> 。</p><ul><li><p>海明码的校验码总是放在 <span class="math inline">\(2^i, i = 0,1,2,3...\)</span></p></li><li><p>把海明码(有效码和校验码的结合)从左到右,从 <strong>1</strong> 开始编号 <span class="math inline">\(C_1C_2n_3C_4n_5n_6n_7C_8...\)</span></p></li><li><p>各检验码负责的部分: <span class="math inline">\(C_i\)</span> 负责检验第 <span class="math inline">\(i\)</span> 位为 1 的编号</p></li><li><p>校验码的数值 = 它所负责的有效码的异或(偶校验规则下)或者凑偶数个 1</p></li><li><p>海明码的纠错过程:</p><ol type="1"><li>接收方先根据数据长度确定 <span class="math inline">\(k\)</span></li><li>增设检验位 <span class="math inline">\(P_i\)</span> ,比如假设此时 <span class="math inline">\(k = 3\)</span> ,则新的检验码为 <span class="math inline">\(P_4P_2P_1\)</span></li><li>根据检验码的规则计算 <span class="math inline">\(P_i\)</span> ,比如偶规则下, <span class="math inline">\(P_1 = 1\oplus 3 \oplus 5\oplus7\)</span></li><li>得到二进制表示的检验码 <span class="math inline">\(P\)</span> ,换算成 10 进制即可得到哪一位出错了。如果是全 0 则表示无错误</li></ol></li><li><p>海明距离:是字符串相对于同样长度的零字符串的汉明距离,也就是说,它是字符串中非零的元素个数:对于二进制字符串来说,就是 1 的个数,所以 11101 的汉明重量是 4。</p></li></ul><h3 id="提高访存速度的措施">提高访存速度的措施</h3><blockquote><ol type="1"><li>采用高速组件</li><li>采用层次结构 Cache-主存</li><li>调整主存结构</li></ol></blockquote><p>下面介绍通过调整主存结构的方式:</p><ol type="1"><li><p>单体多字系统</p><p>将存储器的和 CPU 的存储控制器的位宽设置为 CPU 字长(寄存器宽度)的整数倍,一次性可以读出多条指令或数据,缺点是写的时候,如果仅写一个字的内容比较麻烦,需要相应的硬件配合。</p></li><li><p>多体并行系统 主要思想是采用多个存储器并行工作,CPU 访问第一个存储器后,在读到数据前,无需等待,直接访问下一个存储器。这样一轮下来,第一个存储器已经准备好了数据,并能够接收下一次访问(存取周期已到),于是再来一轮。如此,CPU 和存储器都不会空闲。</p></li></ol>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>市原大輔走了,但没完全走。崛起你真的好温柔。</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220630231342.jpg" alt="91953835_p0_master1200" style="zoom:80%;" /></p></summary>
<category term="计算机组成原理" scheme="http://lapras.xyz/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="硬件" scheme="http://lapras.xyz/tags/%E7%A1%AC%E4%BB%B6/"/>
</entry>
<entry>
<title>Python 求解规划类问题</title>
<link href="http://lapras.xyz/2022/06/29/d7cb7c5c.html"/>
<id>http://lapras.xyz/2022/06/29/d7cb7c5c.html</id>
<published>2022-06-29T15:08:00.000Z</published>
<updated>2022-08-21T07:21:56.319Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>摸鱼,等崛起的神威太刀。</p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%B3%A1%E5%A3%B6%E9%BE%99%E5%A4%AA%E5%88%80.jpg" alt="89249011_p1_master1200" style="zoom: 67%;" / loading="lazy"></p><span id="more"></span><h2 id="线性规划">线性规划</h2><p><strong>生产问题,投料问题</strong></p><p>线性规划求解需要确定目标函数( <span class="math inline">\(max,min\)</span> )和约束条件( <span class="math inline">\(s.t.\)</span> )</p><p>求解前应该转为标准形式,即不等式约束,等式约束以及范围约束: <span class="math display">\[\min \mathrm{c}^{T} x \\\begin{equation}\text { s.t. }\left\{\begin{array}{c}A x \leq b \\A e q * x = b e q \\l b \leq x \leq u b\end{array}\right.\end{equation}\]</span></p><ul><li><span class="math inline">\(c^T\)</span> 为目标函数系数 <strong>向量</strong></li><li><span class="math inline">\(A\)</span> 为不等式组系数 <strong>矩阵</strong>, <span class="math inline">\(b\)</span> 为不等式组常数 <strong>向量</strong></li><li><span class="math inline">\(Aeq\)</span> 为等式组系数 <strong>矩阵</strong>, <span class="math inline">\(beq\)</span> 为等式组系数 <strong>向量</strong></li><li><span class="math inline">\(lb, ub\)</span> 为下界和上界 <strong>向量</strong></li></ul><pre class="language-matlab" data-language="matlab"><code class="language-matlab"><span class="token punctuation">[</span>x<span class="token punctuation">,</span>fval<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">linprog</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> A<span class="token punctuation">,</span> b<span class="token punctuation">,</span> Aeq<span class="token punctuation">,</span> beq<span class="token punctuation">,</span> lb<span class="token punctuation">,</span> ub<span class="token punctuation">,</span> X0<span class="token punctuation">)</span></code></pre><ul><li><p>标准形式为求解最小值,如果求解最大值等价于求 <span class="math inline">\(-\min \mathrm{c}^{T} x\)</span> 的最小值,最后结果再给个相反数即可</p></li><li><p>标准形式的不等式为 <strong>小等于</strong></p></li><li><p><code>x</code> 为最小值的系数 <strong>向量</strong>,<code>fval</code> 为最小值</p></li><li><p><code>X0</code> 为迭代初值,可省略</p></li></ul><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> scipy <span class="token keyword">import</span> optimize<span class="token keyword">import</span> numpy <span class="token keyword">as</span> np<span class="token keyword">def</span> <span class="token function">LinearProgram</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> A<span class="token punctuation">,</span> b<span class="token punctuation">,</span> Aeq<span class="token punctuation">,</span> beq<span class="token punctuation">,</span> bounds<span class="token operator">=</span><span class="token boolean">None</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token comment"># 求解函数</span> res <span class="token operator">=</span> optimize<span class="token punctuation">.</span>linprog<span class="token punctuation">(</span>c<span class="token punctuation">,</span> A<span class="token punctuation">,</span> b<span class="token punctuation">,</span> Aeq<span class="token punctuation">,</span> beq<span class="token punctuation">,</span> bounds<span class="token punctuation">)</span> <span class="token comment"># 目标函数最小值</span> <span class="token keyword">print</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span> <span class="token comment"># 最优解</span> <span class="token keyword">print</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>x<span class="token punctuation">)</span></code></pre><ul><li>标准形式为求解最小值,如果求解最大值等价于求 <span class="math inline">\(-\min \mathrm{c}^{T} x\)</span> 的最小值,最后结果再给个相反数即可</li><li>标准形式的不等式为 <strong>小等于</strong></li><li>无上下界约束为默认值</li><li><code>bounds</code> 为二元组列表,每个二元组对应一个 <span class="math inline">\(x\)</span> 的下界和上界,无限制则为 <code>None</code></li></ul><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220421172003.png" alt="线性规划例子" / loading="lazy"><figcaption aria-hidden="true">线性规划例子</figcaption></figure><h2 id="整数规划">整数规划</h2><h3 id="线性整数规划">线性整数规划</h3><p><strong>钢管切割问题</strong></p><p>在线性规划的基础上,加入决策变量为整数的条件</p><p>整数规划求解的基本框架是 <strong>分支定界法</strong>(Branch and bound,BnB)</p><p>首先去除整数约束得到“<strong>松弛模型</strong>”,使用线性规划的方法求解。若有某个变量不是整数,在松弛模型上分别添加约束</p><p>但是 <code>python</code> 实现分支定界较为繁琐,所以选择使用 <code>Plup</code> 这个专门用来解线性规划问题的库</p><pre class="language-matlab" data-language="matlab"><code class="language-matlab"><span class="token punctuation">[</span>x<span class="token punctuation">,</span>fval<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">intlinprog</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> intcon<span class="token punctuation">,</span> A<span class="token punctuation">,</span> b<span class="token punctuation">,</span> Aeq<span class="token punctuation">,</span> beq<span class="token punctuation">,</span> lb<span class="token punctuation">,</span> ub<span class="token punctuation">)</span></code></pre><ul><li><code>intcon</code> 为一个 <strong>向量</strong>,指定那些决策变量是整数</li></ul><h3 id="规划">0-1 规划</h3><p><strong>背包问题,指派问题</strong></p><p>特殊的线性整数规划,即决策变量只取 0 或者 1</p><p>显然,只需要对线性整数规划的上下界进行约束即可</p><h3 id="plup">Plup</h3><blockquote><p>真好用啊,符合人类直觉的限制方式</p><p>Pulp 本质上是求解器的接口,具体的求解是依赖优化器实现的</p></blockquote><ol type="1"><li>定义一个规划问题类 <code>LpProblem</code></li></ol><pre class="language-python" data-language="python"><code class="language-python">myProb <span class="token operator">=</span> pulp<span class="token punctuation">.</span>LpProblem<span class="token punctuation">(</span><span class="token string">"ProblemName"</span><span class="token punctuation">,</span> sense<span class="token operator">=</span>pulp<span class="token punctuation">.</span>LpMaximize<span class="token punctuation">)</span></code></pre><ul><li><code>sense</code> 可为:<code>LpMinimize</code> <code>LpMaximize</code> 对最小值问题和最大值问题</li></ul><ol start="2" type="1"><li>定义决策变量 <code>LpVariable</code></li></ol><pre class="language-python" data-language="python"><code class="language-python">x1 <span class="token operator">=</span> pulp<span class="token punctuation">.</span>LpVariable<span class="token punctuation">(</span><span class="token string">'x1'</span><span class="token punctuation">,</span> lowBound<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">,</span> upBound<span class="token operator">=</span><span class="token number">7</span><span class="token punctuation">,</span> cat<span class="token operator">=</span>pulp<span class="token punctuation">.</span>LpConstraint<span class="token punctuation">)</span> x2 <span class="token operator">=</span> pulp<span class="token punctuation">.</span>LpVariable<span class="token punctuation">(</span><span class="token string">'x2'</span><span class="token punctuation">,</span> lowBound<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">,</span> cat<span class="token operator">=</span>pulp<span class="token punctuation">.</span>LpInteger<span class="token punctuation">)</span>x3 <span class="token operator">=</span> pulp<span class="token punctuation">.</span>LpVariable<span class="token punctuation">(</span><span class="token string">'x3'</span><span class="token punctuation">,</span> cat<span class="token operator">=</span>pulp<span class="token punctuation">.</span>LpBinary<span class="token punctuation">)</span> </code></pre><ul><li>上下界缺省为无穷</li><li><code>cat</code> 设定变量类型,用于解决小数、整数以及 0-1 规划</li></ul><ol start="3" type="1"><li>添加目标函数</li></ol><pre class="language-python" data-language="python"><code class="language-python">myProb <span class="token operator">+=</span> <span class="token number">2</span><span class="token operator">*</span>x1 <span class="token operator">+</span> <span class="token number">3</span><span class="token operator">*</span>x2 <span class="token operator">-</span> <span class="token number">5</span><span class="token operator">*</span>x3<span class="token comment"># 设置目标函数</span></code></pre><ul><li>添加目标函数使用 “之前定义的规划问题类 += 目标函数式” 格式。</li></ul><ol start="4" type="1"><li>添加约束条件</li></ol><pre class="language-python" data-language="python"><code class="language-python">myProb <span class="token operator">+=</span> <span class="token number">2</span><span class="token operator">*</span>x1 <span class="token operator">-</span> <span class="token number">5</span><span class="token operator">*</span>x2 <span class="token operator">+</span> x3 <span class="token operator">>=</span> <span class="token number">10</span> <span class="token comment"># 不等式约束</span>myProb <span class="token operator">+=</span> x1 <span class="token operator">+</span> <span class="token number">3</span><span class="token operator">*</span>x2 <span class="token operator">+</span> x3 <span class="token operator"><=</span> <span class="token number">12</span> <span class="token comment"># 不等式约束</span>myProb <span class="token operator">+=</span> x1 <span class="token operator">+</span> x2 <span class="token operator">+</span> x3 <span class="token operator">==</span> <span class="token number">7</span> <span class="token comment"># 等式约束</span></code></pre><ul><li>约束式只能为 <code>==</code> <code>>=</code> <code><=</code></li></ul><ol start="5" type="1"><li>求解</li></ol><pre class="language-python" data-language="python"><code class="language-python">myProb<span class="token punctuation">.</span>solve<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token comment"># myProb.solve(pulp.GUROBI_CMD())</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"求解状态:"</span><span class="token punctuation">,</span> pulp<span class="token punctuation">.</span>LpStatus<span class="token punctuation">[</span>myProb<span class="token punctuation">.</span>status<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">for</span> v <span class="token keyword">in</span> myProb<span class="token punctuation">.</span>variables<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">print</span><span class="token punctuation">(</span>v<span class="token punctuation">.</span>name<span class="token punctuation">,</span> <span class="token string">"="</span><span class="token punctuation">,</span> v<span class="token punctuation">.</span>varValue<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"目标值="</span><span class="token punctuation">,</span> pulp<span class="token punctuation">.</span>value<span class="token punctuation">(</span>myProb<span class="token punctuation">.</span>objective<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre><ul><li>PuLP 默认采用 CBC 求解器来求解优化问题</li><li>可以调用其它的优化器来求解,如:GLPK,COIN CLP/CBC,CPLEX,和 GUROBI,但需要另外安装。</li></ul><h3 id="cvxpy">CvxPY</h3><blockquote><p>支持较多变量需要用到矩阵乘法的规划问题</p></blockquote><ol type="1"><li>定义系数矩阵</li></ol><pre class="language-python" data-language="python"><code class="language-python">C <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">[</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span>A <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">[</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span>B <span class="token operator">=</span> np<span class="token punctuation">.</span>array<span class="token punctuation">[</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span></code></pre><ol start="2" type="1"><li>声明决策变量</li></ol><pre class="language-python" data-language="python"><code class="language-python">x <span class="token operator">=</span> cp<span class="token punctuation">.</span>Variable<span class="token punctuation">(</span>n<span class="token punctuation">,</span>integer <span class="token operator">=</span> <span class="token boolean">True</span><span class="token punctuation">)</span></code></pre><ul><li><span class="math inline">\(n\)</span> 为变量长度</li><li>可以声明决策变量类型<ul><li><code>neg pos nonneg nonpos</code>:负数 正数 非负数 非正数</li><li><code>boolean</code>:布尔变量</li></ul></li></ul><ol start="3" type="1"><li>声明问题</li></ol><pre class="language-python" data-language="python"><code class="language-python">objective <span class="token operator">=</span> cp<span class="token punctuation">.</span>Minimize<span class="token punctuation">(</span>cp<span class="token punctuation">.</span><span class="token builtin">sum</span><span class="token punctuation">(</span>C<span class="token operator">*</span>x<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre><ul><li><code>Minimize</code> 和 <code>Maximize</code></li></ul><ol start="4" type="1"><li>定义约束</li></ol><pre class="language-python" data-language="python"><code class="language-python"><span class="token comment"># 开始拼接约束方程</span><span class="token comment"># B是右边的常数项</span>B <span class="token operator">=</span> np<span class="token punctuation">.</span>concatenate<span class="token punctuation">(</span><span class="token punctuation">(</span>B1<span class="token punctuation">,</span> B2<span class="token punctuation">,</span> B3<span class="token punctuation">,</span> B4<span class="token punctuation">,</span> B5<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment"># print(B)</span><span class="token comment"># A是左边的约束项</span>A <span class="token operator">=</span> np<span class="token punctuation">.</span>vstack<span class="token punctuation">(</span><span class="token punctuation">[</span>A1<span class="token punctuation">,</span> A2<span class="token punctuation">,</span> A3<span class="token punctuation">,</span> A4<span class="token punctuation">,</span> A5<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token comment"># print(A)</span><span class="token comment"># Be是右边的等式约束项</span>Be <span class="token operator">=</span> np<span class="token punctuation">.</span>concatenate<span class="token punctuation">(</span><span class="token punctuation">(</span>Be1<span class="token punctuation">,</span> Be2<span class="token punctuation">,</span>Be3<span class="token punctuation">,</span>Be4<span class="token punctuation">,</span>Be5<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment"># print(Be)</span><span class="token comment"># Ae是左边的等式约束项</span>Ae <span class="token operator">=</span> np<span class="token punctuation">.</span>vstack<span class="token punctuation">(</span><span class="token punctuation">[</span>Ae1<span class="token punctuation">,</span> Ae2<span class="token punctuation">,</span>Ae3<span class="token punctuation">,</span>Ae4<span class="token punctuation">,</span>Ae5<span class="token punctuation">]</span><span class="token punctuation">)</span></code></pre><ol start="5" type="1"><li>定义求解器</li></ol><pre class="language-python" data-language="python"><code class="language-python">constraints <span class="token operator">=</span> <span class="token punctuation">[</span>Ae @ x <span class="token operator">==</span> Be<span class="token punctuation">,</span> A @ x <span class="token operator">>=</span> B<span class="token punctuation">]</span>prob <span class="token operator">=</span> cp<span class="token punctuation">.</span>Problem<span class="token punctuation">(</span>objects<span class="token punctuation">,</span> constraints<span class="token punctuation">)</span>prob<span class="token punctuation">.</span>solve<span class="token punctuation">(</span>solver<span class="token operator">=</span>cp<span class="token punctuation">.</span>GUROBI<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"Status:"</span><span class="token punctuation">,</span> prob<span class="token punctuation">.</span>status<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"Optimal value"</span><span class="token punctuation">,</span> prob<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"Optimal var\n"</span><span class="token punctuation">,</span> x<span class="token punctuation">.</span>value<span class="token punctuation">.</span>reshape<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre><h2 id="非线性规划">非线性规划</h2><p>如果目标函数或者约束条件存在非线性函数,即为非线性规划情况 <span class="math display">\[\min \mathrm{c}^{T} x \\\begin{equation}\text { s.t. }\left\{\begin{array}{c}A x \leq b \\A e q * x = b e q \\C(x) \leq 0 \\Ceq(x) = 0 \\l b \leq x \leq u b\end{array}\right.\end{equation}\]</span></p><p>不难发现,比起线性规划的标准形式,非线性规划多了 <strong>非线性不等式约束</strong> <span class="math inline">\(C(x) \leq 0\)</span> 以及 <strong>非线性等式约束</strong> <span class="math inline">\(Ceq(x)= 0\)</span></p><ul><li><p><span class="math inline">\(c^T\)</span> 为目标函数系数 <strong>向量</strong></p></li><li><p><span class="math inline">\(C(x)\)</span> 为非线性函数 <strong>向量</strong></p></li><li><p><span class="math inline">\(Ceq\)</span> 为非线性函数向量</p></li></ul><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/20220422103411.png" alt="非线性规划例子" / loading="lazy"><figcaption aria-hidden="true">非线性规划例子</figcaption></figure><pre class="language-matlab" data-language="matlab"><code class="language-matlab"><span class="token punctuation">[</span>x<span class="token punctuation">,</span> fval<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">fmincon</span><span class="token punctuation">(</span><span class="token operator">@</span>fun<span class="token punctuation">,</span> X0<span class="token punctuation">,</span> A<span class="token punctuation">,</span> b<span class="token punctuation">,</span> Aeq<span class="token punctuation">,</span> beq<span class="token punctuation">,</span> lb<span class="token punctuation">,</span> ub<span class="token punctuation">,</span> <span class="token operator">@</span>nonlfun<span class="token punctuation">,</span> OPTION<span class="token punctuation">)</span></code></pre><ul><li>算法本身求取的是局部最优,所以预期的初始值 <code>X0</code> 非常重要</li><li>如果要求全局最优:<ul><li>给定不同的初始值,得到“全局最优解”</li><li>先用蒙特卡罗模拟,将该解作为初始值来求取最优解(推荐)</li></ul></li><li><code>OPTION</code> 可以指定使用的求解算法,<strong>通过改变算法,可以体现你的模型稳定性</strong></li><li><code>@fun</code> 和 <code>@nonlfun</code> 需要用额外的 <code>.m</code> 文件定义</li></ul><p>非线性规划可以依据目标函数类型简单分为两种,凸函数和非凸函数</p><ul><li>凸函数可以使用 <code>cvxpy</code> 库</li><li>非凸函数没有特定的算法可以尝试寻找极值:<ul><li>纯数学</li><li>神经网络,深度学习</li><li><code>scipy.optimize.minimize</code></li><li><code>gekko</code></li></ul></li></ul><h3 id="scipy-optimize">SciPy optimize</h3><blockquote><p>与 matlab 一样,需要定义目标函数以及约束条件</p></blockquote><p>目标函数</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">objective</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">''' args 为决策变量的系数向量 '''</span> a<span class="token punctuation">,</span> b<span class="token punctuation">,</span> c<span class="token punctuation">,</span> d <span class="token operator">=</span> args <span class="token keyword">def</span> <span class="token function">v</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">return</span> a<span class="token operator">*</span>x<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">*</span> d<span class="token operator">*</span>x<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span> <span class="token operator">*</span> <span class="token punctuation">(</span>a<span class="token operator">*</span>x<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+</span> b<span class="token operator">*</span>x<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> c<span class="token operator">*</span>x<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">+</span> c<span class="token operator">*</span>x<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token keyword">return</span> v</code></pre><p>约束条件</p><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">constraints</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token triple-quoted-string string">''' args 为约束条件的常数向量 '''</span> eq1<span class="token punctuation">,</span> ineq1 <span class="token operator">=</span> args cons <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span><span class="token string">'type'</span><span class="token punctuation">:</span> <span class="token string">'ineq'</span><span class="token punctuation">,</span> <span class="token string">'fun'</span><span class="token punctuation">:</span> <span class="token keyword">lambda</span> x<span class="token punctuation">:</span> x<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">*</span> x<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">*</span> x<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span> <span class="token operator">*</span> x<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span> <span class="token operator">-</span> ineq1<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token string">'type'</span><span class="token punctuation">:</span> <span class="token string">'eq'</span><span class="token punctuation">,</span> <span class="token string">'fun'</span><span class="token punctuation">:</span> <span class="token keyword">lambda</span> x<span class="token punctuation">:</span> x<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">**</span><span class="token number">2</span> <span class="token operator">+</span> x<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token operator">**</span><span class="token number">2</span> <span class="token operator">+</span> x<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token operator">**</span><span class="token number">2</span> <span class="token operator">+</span> x<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token operator">**</span><span class="token number">2</span> <span class="token operator">-</span> eq1<span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> cons</code></pre><ul><li><code>type</code> 有 <code>ineq, eq</code> 分别表示不等式约束和等式约束类型</li><li>最后记得减去常数以保证形式都为 0</li><li>注意:在 <code>optimize.minimize</code> 中的 <strong>不等式约束标准形式为大等于</strong>,与 <code>optimize.linprog</code> 相反</li></ul><p>求解</p><pre class="language-PYTHON" data-language="PYTHON"><code class="language-PYTHON">res = minimize(objective(objargs), X0, method='SLSQP',bounds=bounds, constraints=constraints(conargs))</code></pre><ul><li>算法本身求取的是局部最优,所以预期的初始值 <code>X0</code> 非常重要</li><li><code>method</code> 可选多种算法</li></ul><h3 id="gekko">Gekko</h3><blockquote><p>与 Pulp 语法比较接近,属于人类直觉型库</p></blockquote><ol type="1"><li>初始化</li></ol><pre class="language-python" data-language="python"><code class="language-python"><span class="token keyword">from</span> gekko <span class="token keyword">import</span> GEKKOm <span class="token operator">=</span> GEKKO<span class="token punctuation">(</span>remote<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span> <span class="token comment"># 指定求解器</span>m<span class="token punctuation">.</span>options<span class="token punctuation">.</span>SOLVER <span class="token operator">=</span> <span class="token number">3</span></code></pre><ol start="2" type="1"><li>定义决策变量</li></ol><pre class="language-python" data-language="python"><code class="language-python">x1 <span class="token operator">=</span> m<span class="token punctuation">.</span>Var<span class="token punctuation">(</span>lb<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span> ub<span class="token operator">=</span><span class="token number">5</span><span class="token punctuation">)</span>x2 <span class="token operator">=</span> m<span class="token punctuation">.</span>Var<span class="token punctuation">(</span>lb<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span> ub<span class="token operator">=</span><span class="token number">5</span><span class="token punctuation">)</span>x3 <span class="token operator">=</span> m<span class="token punctuation">.</span>Var<span class="token punctuation">(</span>lb<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span> ub<span class="token operator">=</span><span class="token number">5</span><span class="token punctuation">)</span>x4 <span class="token operator">=</span> m<span class="token punctuation">.</span>Var<span class="token punctuation">(</span>lb<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span> ub<span class="token operator">=</span><span class="token number">5</span><span class="token punctuation">)</span></code></pre><ol start="3" type="1"><li>定义约束条件</li></ol><pre class="language-python" data-language="python"><code class="language-python">m<span class="token punctuation">.</span>Equation<span class="token punctuation">(</span>x1 <span class="token operator">*</span> x2 <span class="token operator">*</span> x3 <span class="token operator">*</span> x4 <span class="token operator">>=</span> <span class="token number">25</span><span class="token punctuation">)</span>m<span class="token punctuation">.</span>Equation<span class="token punctuation">(</span>x1<span class="token operator">**</span><span class="token number">2</span> <span class="token operator">+</span> x2<span class="token operator">**</span><span class="token number">2</span> <span class="token operator">+</span> x3<span class="token operator">**</span><span class="token number">2</span> <span class="token operator">+</span> x4<span class="token operator">**</span><span class="token number">2</span> <span class="token operator">==</span> <span class="token number">40</span><span class="token punctuation">)</span></code></pre><ol start="4" type="1"><li>定义目标函数</li></ol><pre class="language-python" data-language="python"><code class="language-python">m<span class="token punctuation">.</span>Obj<span class="token punctuation">(</span>x1 <span class="token operator">*</span> x4 <span class="token operator">*</span> <span class="token punctuation">(</span>x1 <span class="token operator">+</span> x2 <span class="token operator">+</span> x3<span class="token punctuation">)</span> <span class="token operator">+</span> x3<span class="token punctuation">)</span></code></pre><ol start="5" type="1"><li>求解</li></ol><pre class="language-python" data-language="python"><code class="language-python"><span class="token comment"># 指定优化器</span>m<span class="token punctuation">.</span>options<span class="token punctuation">.</span>IMODE <span class="token operator">=</span> <span class="token number">3</span><span class="token comment"># 求解</span>m<span class="token punctuation">.</span>solve<span class="token punctuation">(</span>disp<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span> <span class="token comment"># Solve</span><span class="token comment"># 输出结果</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'x1 = '</span><span class="token punctuation">,</span> x1<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'x2 = '</span><span class="token punctuation">,</span> x2<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'x3 = '</span><span class="token punctuation">,</span> x3<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'x4 = '</span><span class="token punctuation">,</span> x4<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">'Objective: '</span> <span class="token operator">+</span> <span class="token builtin">str</span><span class="token punctuation">(</span>m<span class="token punctuation">.</span>options<span class="token punctuation">.</span>objfcnval<span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>摸鱼,等崛起的神威太刀。</p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%B3%A1%E5%A3%B6%E9%BE%99%E5%A4%AA%E5%88%80.jpg" alt="89249011_p1_master1200" style="zoom: 67%;" /></p></summary>
<category term="数学建模" scheme="http://lapras.xyz/categories/%E6%95%B0%E5%AD%A6%E5%BB%BA%E6%A8%A1/"/>
<category term="Python" scheme="http://lapras.xyz/tags/Python/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="数学" scheme="http://lapras.xyz/tags/%E6%95%B0%E5%AD%A6/"/>
</entry>
<entry>
<title>计算机组成原理笔记(二)</title>
<link href="http://lapras.xyz/2022/06/25/6ba75aa8.html"/>
<id>http://lapras.xyz/2022/06/25/6ba75aa8.html</id>
<published>2022-06-25T14:01:07.000Z</published>
<updated>2022-08-21T07:21:56.352Z</updated>
<content type="html"><![CDATA[<h2 id="前言">前言</h2><p>今日 KEEP <del> KFC </del></p><p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%B1%B3%E5%B1%B1%E8%88%9E-eva.jpeg" alt="米山舞 eva" style="zoom:80%;" / loading="lazy"></p><span id="more"></span><h2 id="总线的基本概念">总线的基本概念</h2><blockquote><p><strong>总线</strong>(Bus)是指计算机组件间规范化的交换数据的方式,即以一种通用的方式为各组件提供数据传送和控制逻辑。从另一个角度来看,如果说 <a href="https://zh.wikipedia.org/wiki/主機板">主板</a>(Mother Board)是一座城市,那么总线就像是城市里的公共汽车(bus),能按照固定行车路线,传输来回不停运作的 <a href="https://zh.wikipedia.org/wiki/位元">比特</a>(bit)。这些线路在同一时间内都仅能负责传输一个比特。因此,必须同时采用多条线路才能发送更多资料,而总线可同时传输的资料数就称为宽度(width),以比特为单位,总线宽度愈大,传输性能就愈佳。总线的 <a href="https://zh.wikipedia.org/wiki/頻寬">带宽</a>(即单位时间内可以传输的总资料数)为:总线带宽 = 频率×宽度(Bytes/sec)</p></blockquote><h3 id="为什么使用总线">为什么使用总线</h3><table><thead><tr class="header"><th>连接方式</th><th>硬件资源</th><th>可扩展性</th></tr></thead><tbody><tr class="odd"><td>两两单独连接</td><td>占用引脚多,连线复杂</td><td>需要现有设备提供与新设备之间的接口,扩展起来麻烦</td></tr><tr class="even"><td>总线式连接</td><td>占用引脚少,连线简单</td><td>只需将新设备挂到总线,扩展方便</td></tr></tbody></table><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BA%BF.png" alt="为什么要使用总线" / loading="lazy"><figcaption aria-hidden="true">为什么要使用总线</figcaption></figure><h3 id="总线上信息的传输">总线上信息的传输</h3><p>首先,<strong>在任意时刻一条总线只能有一对部件进行信息传输</strong></p><p>总线信息传输方式可以分为 <strong>串行传输</strong> 和 <strong>并行传输</strong>,字面意思来看,串行就是数据是一位一位的发送 <strong>,</strong> 并行就是数据一组一组的发送。以直觉来看,并行应该是比串行传输速率高的,但事实上现在大部分的芯片都选择串行传输</p><blockquote><p><strong>并行总线由于是多个数据同时传输,需要考虑数据的协同性,这就导致了并行传输的频率不能做的很高</strong>。相对的,串行总线只有一条链路,就可以把频率做的很高,提高传输速度,速度提高了就能够弥补一次只能传输一个数据的缺陷。</p><p>此外,<strong>并行总线两根相邻的链路其数据是同时传输的</strong>,这就会导致它们彼此之间会产生 <strong>严重干扰</strong>,并行的链路越多,干扰越强。因此并行总线需要加强抗干扰的能力,否则传输过程中数据就可能被损坏。如果传输过程中数据故障了,就需要重新对齐数据再传输。而串行总线如果一个数据出错了,只需要重新传输一次就好了,由于串行总线频率高,很快就可以把错误数据重新传输过去。</p><p>再次,由于 <strong>并行总线是多链路一块传输数据</strong>,就需要很多线,接口需要很多针脚,老式计算机里的并行接口做得很大,接线比较宽,针脚非常多。这样一来装机也很麻烦,因为走线不方便、接口体积很大。</p></blockquote><h2 id="总线的分类">总线的分类</h2><h3 id="根据总线位置进行分类">根据总线位置进行分类</h3><ol type="1"><li>片内总线:芯片内部的总线</li><li>系统总线:计算机各部件的信息传输<ol type="1"><li>数据总线:双向,与机器字长、存储字长相关</li><li>地址总线:单向,与存储地址、I/O 地址相关</li><li>控制总线:<ul><li>有出:中断请求、总线请求</li><li>存储器读、存储器写、总线使用权许可、中断确认</li></ul></li></ol></li><li>通信总线:用于 计算机系统之间 或 计算机系统 与 其他系统。<ol type="1"><li>串行传输</li><li>并行传输</li></ol></li></ol><h2 id="总线特性及性能指标">总线特性及性能指标</h2><h3 id="总线特性">总线特性</h3><table><thead><tr class="header"><th>特性分类</th><th>含义</th></tr></thead><tbody><tr class="odd"><td>机械特性</td><td>尺寸、形状、引脚数、引脚的排列顺序等</td></tr><tr class="even"><td>电气特性</td><td>传输方向、有效电平范围等</td></tr><tr class="odd"><td>功能特性</td><td>每根线的功能,如地址、数据、控制等</td></tr><tr class="even"><td>时间特性</td><td>时钟频率、信号的时序关系等</td></tr></tbody></table><h3 id="性能指标">性能指标</h3><ol type="1"><li>总线宽度:数据线的根数,根数越多,同时传输的位数就越多</li><li>标准传输率:每秒传输的最大字节数(MBps)</li><li>时钟类型:同步、不同步</li><li>总线复用:地址线和数据线 <strong>复用</strong>,以减少芯片的管脚数</li><li>信号线数:地址线、数据线和控制线的总和</li><li>总线控制方式:突发、自动、仲裁、逻辑、计数</li><li>其他指标:负载能力</li></ol><h3 id="总线标准">总线标准</h3><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E6%80%BB%E7%BA%BF%E6%A0%87%E5%87%86.png" alt="image-20220627192109975" / loading="lazy"><figcaption aria-hidden="true">image-20220627192109975</figcaption></figure><p>顺便一提,现代总线比如雷电 4 标准已经到 40 Gbps,所以总线标准也是计算机的性能瓶颈之一。</p><h2 id="总线结构">总线结构</h2><h3 id="单总线结构">单总线结构</h3><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%8D%95%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84.png" alt="单总线结构" / loading="lazy"><figcaption aria-hidden="true">单总线结构</figcaption></figure><ul><li>存在主线争用问题</li><li>时间延迟高</li></ul><h3 id="多总线结构">多总线结构</h3><ol type="1"><li><p>面向 CPU 的双总线结构</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E9%9D%A2%E5%90%91CPU%E7%9A%84%E5%8F%8C%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84.png" alt="面向 CPU 的双总线结构" / loading="lazy"><figcaption aria-hidden="true">面向 CPU 的双总线结构</figcaption></figure><ul><li>考虑到 <strong>指令</strong> 和 <strong>数据</strong> 都来自主存,所以用单独的 M 总线保证其交换速度</li><li>假设主存要与 I/O 设备信息传输,就不得不经过 CPU,会打乱 CPU 的信息交换任务</li></ul></li><li><p>面向存储器的双总线结构</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E9%9D%A2%E5%90%91%E5%AD%98%E5%82%A8%E5%99%A8%E7%9A%84%E5%8F%8C%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84.png" alt="面向存储器的双总线结构" / loading="lazy"><figcaption aria-hidden="true">面向存储器的双总线结构</figcaption></figure><ul><li>从主存发出两条总线:存储总线和系统总线</li><li>CPU 和主存也保留了专用总线</li><li>CPU 也可以直接与 I/O 设备交互</li><li>目前还不能做到主存同时使用两根总线</li></ul></li><li><p>使用通道</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E9%80%9A%E9%81%93%E5%8F%8C%E4%B8%BB%E7%BA%BF%E7%BB%93%E6%9E%84.png" alt="通道双主线" / loading="lazy"><figcaption aria-hidden="true">通道双主线</figcaption></figure><ul><li>一般来说通道有自己的控制器,指令等</li></ul></li><li><p>三总线结构</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%89%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84.png" alt="三总线结构" / loading="lazy"><figcaption aria-hidden="true">三总线结构</figcaption></figure><ul><li>在面向 CPU 的双总线结构上,将 I/O 设备分为高速和低速</li><li>将高速设备通过新的 DMA 总线与内存进行直接地信息交换</li></ul></li><li><p>三总线结构-2</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E4%B8%89%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%842" alt="三总线结构 2" / loading="lazy"><figcaption aria-hidden="true">三总线结构 2</figcaption></figure><ul><li>由于内存的进步较于 CPU 缓慢,容易成为计算机的瓶颈点。所以使用 Cache 对内存中常用的指令预先读取,然后单独与 CPU 连接一条局部总线</li><li>系统总线通过一个扩展总线接口连接扩展总线,但这样会影响外部设备的传输速率</li></ul></li><li><p>四总线结构</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%9B%9B%E6%80%BB%E7%BA%BF%E7%BB%93%E6%9E%84.png" alt="四总线结构" / loading="lazy"><figcaption aria-hidden="true">四总线结构</figcaption></figure><ul><li>在三总线-2 的基础上将外设分为高速和低速</li></ul></li></ol><h2 id="总线控制">总线控制</h2><p>总线控制主要解决两个问题:多设备同时申请使用总线的判定(总线判优控制/仲裁)和设备占用总线中保证通讯的正确性(总线通信控制)</p><h3 id="总线判优控制">总线判优控制</h3><p>根据组件在总线中的功能可以分为总设备(模块)和从设备(模块)</p><p>主设备:对总线有控制权,可以发出占用总线的申请</p><p>从设备:没有控制权,只能响应主设备发出的申请</p><p>集中式仲裁:把总线的判优逻辑放在一个部件中,根据查询方式不同,又可以分为:<strong>链式查询、计数器定时查询和独立请求方式</strong></p><p>分布式仲裁:总线的仲裁逻辑分散在与总线连接的各主设备上。典型的例子有 <strong>以太网</strong>,以太网上接入的各台计算机都可以发起通信,为避免无序竞争,它们都需要遵循以太网的仲裁逻辑,即 <strong>载波侦听/冲突检测</strong>。</p><h4 id="链式查询">链式查询</h4><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E9%93%BE%E5%BC%8F%E6%9F%A5%E8%AF%A2.png" alt="链式查询" / loading="lazy"><figcaption aria-hidden="true">链式查询</figcaption></figure><ul><li><p>地址线:从设备查找</p></li><li><p>数据线:数据传输</p></li><li><p>BR 总线:各接口向总线控制部件提出占用请求</p></li><li><p>BG 总线:总线控制部件 <strong>链式地</strong> 查询哪个 I/O 接口提出了占用请求(碰到就停止查询)</p></li><li><p>BS 总线:获得总线使用权的接口利用 BR 总线向总线控制部件,发送一个总线忙碌状态以应答</p></li><li><p>链式查询的顺序就是设备的优先级顺序</p></li><li><p>优点:结构简单,算法简单,增删设备容易</p></li><li><p>缺点:BG 对电路故障特别敏感,速度较慢</p></li></ul><h4 id="计数器定时查询">计数器定时查询</h4><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E8%AE%A1%E6%97%B6%E5%99%A8%E5%AE%9A%E6%97%B6%E6%9F%A5%E8%AF%A2.png" alt="计时器定时查询" / loading="lazy"><figcaption aria-hidden="true">计时器定时查询</figcaption></figure><ul><li>设备地址线:由总线控制部件的 <strong>计数器</strong> 发出信号,通过这个地址来查找某个设备是否发出总线请求</li><li>计数器:各接口向总线控制部件通过 BR 提出占用请求。控制器接受到请求并成功通过后就会启动计数器(初值为 0 或者某个地址),计数器的值通过设备地址线向外输出,查询接口为初值的 I/O 接口。如果没有提出,则计数器++后重复查询,直到找到提出的 I/O 接口,并用 BR 进行应答。</li><li>优点:优先级是优先级较为灵活,比如通过软件的方式设定初值,那么优先级也就随之改变了</li></ul><h4 id="独立请求方式">独立请求方式</h4><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%8B%AC%E7%AB%8B%E8%AF%B7%E6%B1%82%E6%96%B9%E5%BC%8F.png" alt="独立请求方式" / loading="lazy"><figcaption aria-hidden="true">独立请求方式</figcaption></figure><ul><li>任何一个 I/O 接口都增加了两条线 BR BG</li><li>排队器:通过软件的方式在总线控制部件中动态地调整优先级</li><li>缺点:连接复杂</li></ul><h3 id="总线通信控制">总线通信控制</h3><p>总线传输周期:完成一次传输需要的时间</p><ul><li>申请分配:判优问题</li><li>寻址阶段:主设备向从设备给出地址和命令</li><li>传输阶段:主设备和从设备交换数据</li><li>结束阶段:主设备撤销相关信息</li></ul><p>方式:</p><table><thead><tr class="header"><th>通信方式</th><th>特点</th></tr></thead><tbody><tr class="odd"><td>同步通信</td><td>统一定宽定距的时标控制数据传输</td></tr><tr class="even"><td>异步通信</td><td>无统一时标,采用应答方式。主设备发出请求(命令),从设备应答,进而完成数据交换</td></tr><tr class="odd"><td>半同步通信</td><td>引入等待信号,解决不同速度的两个设备之间的通讯,同步异步结合</td></tr><tr class="even"><td>分离式通信</td><td>不在等待时占据总线,提高总线通信的效率</td></tr></tbody></table><h4 id="同步通信">同步通信</h4><ol type="1"><li>同步通信输入</li></ol><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%90%8C%E6%AD%A5%E9%80%9A%E4%BF%A1.png" alt="同步通信" / loading="lazy"><figcaption aria-hidden="true">同步通信</figcaption></figure><ul><li>假设一次数据传输使用了四个时钟周期</li><li><span class="math inline">\(T_1\)</span> 的上升沿之前:主设备发出地址信号并持续</li><li><span class="math inline">\(T_2\)</span> 的上升沿之前:给出读命令</li><li><span class="math inline">\(T_3\)</span> 的上升沿之前:从设备需要将需要数据发送到数据线上</li><li><span class="math inline">\(T_4\)</span> 的上升沿之前:撤销数据、撤销读命令</li><li><span class="math inline">\(T_4\)</span> 结束之前:撤销地址</li></ul><ol start="2" type="1"><li><p>同步通信输出</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%90%8C%E6%AD%A5%E9%80%9A%E4%BF%A1%E8%BE%93%E5%87%BA.png" alt="image-20220627210825638" / loading="lazy"><figcaption aria-hidden="true">image-20220627210825638</figcaption></figure><ul><li>假设一次数据传输使用了四个时钟周期</li><li><span class="math inline">\(T_1\)</span> 的上升沿之前:主设备发出地址信号并持续</li><li><span class="math inline">\(T_1\)</span> 的下降沿之前:给出数据到数据线上</li><li><span class="math inline">\(T_2\)</span> 的上升沿之前:给出写命令</li><li><span class="math inline">\(T_4\)</span> 的上升沿之前:撤销数据、撤销写命令</li><li><span class="math inline">\(T_4\)</span> 结束之前:撤销地址</li></ul></li></ol><p>同步通讯特点:需要选择最慢的设备(模块)作为统一通讯的时标,所以通常应用于总线长度短(长度越长频率越低)且各个模块存取时间较为一致。</p><h4 id="异步通讯">异步通讯</h4><p>异步通讯中根据应答信号是否互锁,即请求和回答信号的建立和撤消是否互相依赖,异步通讯可分为三种类型:非 <strong>互锁通讯、半互锁通讯和全互锁通讯</strong>。</p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%BC%82%E6%AD%A5%E9%80%9A%E8%AE%AF.png" alt="异步通讯" / loading="lazy"><figcaption aria-hidden="true">异步通讯</figcaption></figure><ul><li>锁:可以简单理解为状态锁,即保持发送请求这个状态。</li><li>不互锁:主设备发出请求信号,经过一段时间(主设备觉得从设备差不多收到)后,就撤销请求信号。从设备同理,接收到请求信号后,经过一段时间,撤销响应信号。<strong>即主设备主观、从设备主观</strong></li><li>半互锁:主设备发出请求信号,直到从设备发出应答信号后才能撤销。而从设备无需等待主设备发出撤销信号,经过一段时间后,撤销响应信号。<strong>即主设备客观、从设备主观</strong></li><li>全互锁:主设备发出请求信号,直到从设备发出应答信号后才能撤销。从设备发出响应信号,知道主设备发出撤销信号后才能撤销响应信号。<strong>即主设备客观、从设备客观</strong></li><li>显然互锁方式不同,传输的速率和可靠程度亦不同</li></ul><h4 id="半同步通信">半同步通信</h4><p>同步特点:发送方用系统时钟前沿 <strong>发信号</strong>,接收方用系统时钟后沿 <strong>判断、识别</strong></p><p>异步特点:允许不同速度的模块一同工作,增加了一条“等待”响应信号 <span class="math inline">\(\overline {WAIT}\)</span></p><figure><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E5%8D%8A%E5%90%8C%E6%AD%A5%E9%80%9A%E4%BF%A1%E6%96%B9%E5%BC%8F.png" alt="半同步通信读取" / loading="lazy"><figcaption aria-hidden="true">半同步通信读取</figcaption></figure><ul><li><span class="math inline">\(T_1\)</span> 的上升沿之前:主设备发出地址信号并持续</li><li><span class="math inline">\(T_2\)</span> 的上升沿之前:给出读命令</li><li><span class="math inline">\(T_3\)</span> 的上升沿之前:如果从设备无法准备好数据,则给出 <span class="math inline">\(\overline {WAIT}\)</span> 告知 CPU 进行等待,CPU 会插入 <span class="math inline">\(T_w\)</span> 时钟周期。直到某次检测 <span class="math inline">\(WAIT\)</span> 信号为 1,则进入 <span class="math inline">\(T_3\)</span></li><li><span class="math inline">\(T_4\)</span> 的上升沿之前:撤销读命令、撤销数据</li><li><span class="math inline">\(T_4\)</span> 的上升沿之前:撤销地址</li></ul><h4 id="分离式通信">分离式通信</h4><p>上述三种通信控制方式,准备数据的时候总线都没有被占用,这就造成了浪费。于是乎,我们将一个完整的总线传输周期分为两个小周期,<strong>放弃等待数据这段时间的总线占用</strong>。那么这么判断何时从设备准备好数据了呢?所以在分离式通信中,<strong>每个设备都能作为主设备发出请求信号</strong>,这样从设备就能正常地提供数据以继续流程。</p><ol type="1"><li><p>主设备 发出地址和命令占用总线,使用完后主设备放弃总线,从设备进行准备。</p></li><li><p>如果 从设备 准备好数据,<strong>从设备会化身为主设备</strong> 向总线发出请求</p></li></ol><p>特点:</p><ol type="1"><li>各模块均有权有权申请占用总线</li><li>采用同步方式通信,不等对方回答</li><li><strong>准备数据时不占用总线</strong></li><li>总线被占用时,无空闲</li></ol>]]></content>
<summary type="html"><h2 id="前言">前言</h2>
<p>今日 KEEP <del> KFC </del></p>
<p><img src="https://imgbed-1304793179.cos.ap-nanjing.myqcloud.com/typora/%E7%B1%B3%E5%B1%B1%E8%88%9E-eva.jpeg" alt="米山舞 eva" style="zoom:80%;" /></p></summary>
<category term="计算机组成原理" scheme="http://lapras.xyz/categories/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90%E5%8E%9F%E7%90%86/"/>
<category term="笔记" scheme="http://lapras.xyz/tags/%E7%AC%94%E8%AE%B0/"/>
<category term="硬件" scheme="http://lapras.xyz/tags/%E7%A1%AC%E4%BB%B6/"/>
</entry>
</feed>