-
Notifications
You must be signed in to change notification settings - Fork 154
/
Copy path2.2.html
1376 lines (856 loc) · 117 KB
/
2.2.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML>
<html lang="" >
<head>
<meta charset="UTF-8">
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>RTFSC(2) · GitBook</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="description" content="">
<meta name="generator" content="GitBook 3.2.3">
<link rel="stylesheet" href="gitbook/style.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-intopic-toc/style.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-page-footer-ex/style/plugin.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-callouts/plugin.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-highlight/website.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-search/search.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-fontsettings/website.css">
<link rel="stylesheet" href="gitbook/gitbook-plugin-theme-comscore/test.css">
<link rel="stylesheet" href="styles.css">
<meta name="HandheldFriendly" content="true"/>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="gitbook/images/apple-touch-icon-precomposed-152.png">
<link rel="shortcut icon" href="gitbook/images/favicon.ico" type="image/x-icon">
<link rel="next" href="2.3.html" />
<link rel="prev" href="2.1.html" />
</head>
<body>
<div class="book">
<div class="book-summary">
<div id="book-search-input" role="search">
<input type="text" placeholder="Type to search" />
</div>
<nav role="navigation">
<ul class="summary">
<li class="chapter " data-level="1.1" data-path="index.html">
<a href="index.html">
Introduction
</a>
</li>
<li class="chapter " data-level="1.2" data-path="PA0.html">
<a href="PA0.html">
PA0 - 世界诞生的前夜: 开发环境配置
</a>
<ul class="articles">
<li class="chapter " data-level="1.2.1" data-path="0.1.html">
<a href="0.1.html">
Installing GNU/Linux
</a>
</li>
<li class="chapter " data-level="1.2.2" data-path="0.2.html">
<a href="0.2.html">
First Exploration with GNU/Linux
</a>
</li>
<li class="chapter " data-level="1.2.3" data-path="0.3.html">
<a href="0.3.html">
Installing Tools
</a>
</li>
<li class="chapter " data-level="1.2.4" data-path="0.4.html">
<a href="0.4.html">
Configuring vim
</a>
</li>
<li class="chapter " data-level="1.2.5" data-path="0.5.html">
<a href="0.5.html">
More Exploration
</a>
</li>
<li class="chapter " data-level="1.2.6" data-path="0.6.html">
<a href="0.6.html">
Getting Source Code for PAs
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.3" data-path="PA1.html">
<a href="PA1.html">
PA1 - 开天辟地的篇章: 最简单的计算机
</a>
<ul class="articles">
<li class="chapter " data-level="1.3.1" data-path="1.1.html">
<a href="1.1.html">
在开始愉快的PA之旅之前
</a>
</li>
<li class="chapter " data-level="1.3.2" data-path="1.2.html">
<a href="1.2.html">
开天辟地的篇章
</a>
</li>
<li class="chapter " data-level="1.3.3" data-path="1.3.html">
<a href="1.3.html">
RTFSC
</a>
</li>
<li class="chapter " data-level="1.3.4" data-path="1.4.html">
<a href="1.4.html">
基础设施
</a>
</li>
<li class="chapter " data-level="1.3.5" data-path="1.5.html">
<a href="1.5.html">
表达式求值
</a>
</li>
<li class="chapter " data-level="1.3.6" data-path="1.6.html">
<a href="1.6.html">
监视点
</a>
</li>
<li class="chapter " data-level="1.3.7" data-path="1.7.html">
<a href="1.7.html">
如何阅读手册
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.4" data-path="PA2.html">
<a href="PA2.html">
PA2 - 简单复杂的机器: 冯诺依曼计算机系统
</a>
<ul class="articles">
<li class="chapter " data-level="1.4.1" data-path="2.1.html">
<a href="2.1.html">
不停计算的机器
</a>
</li>
<li class="chapter active" data-level="1.4.2" data-path="2.2.html">
<a href="2.2.html">
RTFSC(2)
</a>
</li>
<li class="chapter " data-level="1.4.3" data-path="2.3.html">
<a href="2.3.html">
程序, 运行时环境与AM
</a>
</li>
<li class="chapter " data-level="1.4.4" data-path="2.4.html">
<a href="2.4.html">
基础设施(2)
</a>
</li>
<li class="chapter " data-level="1.4.5" data-path="2.5.html">
<a href="2.5.html">
输入输出
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.5" data-path="PA3.html">
<a href="PA3.html">
PA3 - 穿越时空的旅程: 批处理系统
</a>
<ul class="articles">
<li class="chapter " data-level="1.5.1" data-path="3.1.html">
<a href="3.1.html">
最简单的操作系统
</a>
</li>
<li class="chapter " data-level="1.5.2" data-path="3.2.html">
<a href="3.2.html">
穿越时空的旅程
</a>
</li>
<li class="chapter " data-level="1.5.3" data-path="3.3.html">
<a href="3.3.html">
用户程序和系统调用
</a>
</li>
<li class="chapter " data-level="1.5.4" data-path="3.4.html">
<a href="3.4.html">
文件系统
</a>
</li>
<li class="chapter " data-level="1.5.5" data-path="3.5.html">
<a href="3.5.html">
精彩纷呈的应用程序
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.6" data-path="PA4.html">
<a href="PA4.html">
PA4 - 虚实交错的魔法: 分时多任务
</a>
<ul class="articles">
<li class="chapter " data-level="1.6.1" data-path="4.1.html">
<a href="4.1.html">
多道程序
</a>
</li>
<li class="chapter " data-level="1.6.2" data-path="4.2.html">
<a href="4.2.html">
虚实交错的魔法
</a>
</li>
<li class="chapter " data-level="1.6.3" data-path="4.3.html">
<a href="4.3.html">
超越容量的界限
</a>
</li>
<li class="chapter " data-level="1.6.4" data-path="4.4.html">
<a href="4.4.html">
来自外部的声音
</a>
</li>
<li class="chapter " data-level="1.6.5" data-path="4.5.html">
<a href="4.5.html">
编写不朽的传奇
</a>
</li>
</ul>
</li>
<li class="chapter " data-level="1.7" data-path="blank.html">
<a href="blank.html">
杂项
</a>
<ul class="articles">
<li class="chapter " data-level="1.7.1" data-path="FAQ.html">
<a href="FAQ.html">
常见问题(FAQ)
</a>
</li>
<li class="chapter " data-level="1.7.2" data-path="why.html">
<a href="why.html">
为什么要学习计算机系统基础
</a>
</li>
<li class="chapter " data-level="1.7.3" data-path="linux.html">
<a href="linux.html">
Linux入门教程
</a>
</li>
<li class="chapter " data-level="1.7.4" data-path="man.html">
<a href="man.html">
man入门教程
</a>
</li>
<li class="chapter " data-level="1.7.5" data-path="git.html">
<a href="git.html">
git入门教程
</a>
</li>
<li class="chapter " data-level="1.7.6" data-path="nemu-isa-api.html">
<a href="nemu-isa-api.html">
NEMU ISA相关API说明文档
</a>
</li>
<li class="chapter " data-level="1.7.7" data-path="changelog.html">
<a href="changelog.html">
更新日志
</a>
</li>
<li class="chapter " data-level="1.7.8" data-path="i386-intro.html">
<a href="i386-intro.html">
i386手册指令集阅读指南
</a>
</li>
<li class="chapter " data-level="1.7.9" data-path="exec.html">
<a href="exec.html">
指令执行例子
</a>
</li>
</ul>
</li>
<li class="divider"></li>
<li>
<a href="https://www.gitbook.com" target="blank" class="gitbook-link">
Published with GitBook
</a>
</li>
</ul>
</nav>
</div>
<div class="book-body">
<div class="body-inner">
<div class="book-header" role="navigation">
<!-- Title -->
<h1>
<i class="fa fa-circle-o-notch fa-spin"></i>
<a href="." >RTFSC(2)</a>
</h1>
</div>
<div class="page-wrapper" tabindex="-1" role="main">
<div class="page-inner">
<div id="book-search-results">
<div class="search-noresults">
<section class="normal markdown-section">
<h2 id="rtfm">RTFM</h2>
<p>我们在上一小节中已经在概念上介绍了一条指令具体如何执行, 其中有的概念甚至显而易见得难以展开.
但当我们决定往TRM中添加各种高效指令的同时,
也意味着我们无法回避繁琐的细节.</p>
<p>首先你需要了解指令确切的行为, 为此, 你需要阅读生存手册中指令集相关的章节.
具体地, 无论你选择何种ISA, 相应手册中一般都会有以下内容, 尝试RTFM并寻找这些内容的位置:</p>
<ul>
<li>每一条指令具体行为的描述</li>
<li>指令opcode的编码表格</li>
</ul>
<p>特别地, 由于x86的指令集的复杂性,
我们为选择x86的同学提供了<a href="i386-intro.html">一个简单的阅读教程</a>.</p>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="risc---与cisc平行的另一个世界"><i class="fa fa-comment-o"></i> RISC - 与CISC平行的另一个世界</h5></div><div class="panel-body"><p>你是否觉得x86指令集的格式特别复杂?
这其实是CISC的一个特性, 不惜使用复杂的指令格式, 牺牲硬件的开发成本,
也要使得一条指令可以多做事情, 从而提高代码的密度, 减小程序的大小.
随着时代的发展, 架构师发现CISC中复杂的控制逻辑不利于提高处理器的性能, 于是RISC应运而生.
RISC的宗旨就是简单, 指令少, 指令长度固定, 指令格式统一, 这和KISS法则有异曲同工之妙.
<a href="http://cs.stanford.edu/people/eroberts/courses/soco/projects/risc/risccisc" target="_blank">这里</a>有一篇对比RISC和CISC的小短文.</p><p>另外值得推荐的是<a href="http://blog.sciencenet.cn/blog-414166-763326.html" target="_blank">这篇文章</a>, 里面讲述了一个从RISC世界诞生,
到与CISC世界融为一体的故事, 体会一下RISC的诞生对计算机体系结构发展的里程碑意义.</p></div></div>
<p>如果你非常幸运地选择了riscv32, 你会发现目前只需要阅读很少部分的手册内容就可以了:
在PA中, riscv32的客户程序只会由RV32I和RV32M两类指令组成.
这得益于RISC-V指令集的设计理念 - 模块化.</p>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="risc-v---一款设计精巧的指令集"><i class="fa fa-comment-o"></i> RISC-V - 一款设计精巧的指令集</h5></div><div class="panel-body"><p>RISC-V是一款非常年轻的指令集 - 第一版RISC-V是在2011年5月由UC Berkeley的研究团队提出的,
至今已经风靡全球. 开放性是RISC-V的最大卖点, 就连ARM和MIPS也都为之震撼, 甚至还因竞争关系而互撕...
<a href="http://blog.sciencenet.cn/blog-414166-1089206.html" target="_blank">这篇文章</a>叙述了RISC-V的理念以及成长的一些历史.</p><p>当然, 这些和处于教学领域的PA其实没太大关系. 关键是</p><ul>
<li>RISC-V真的很简单.</li>
<li>简单之余, 还有非常多对程序运行深思熟虑的考量.
如果你阅读RISC-V的官方手册, 你就会发现里面阐述了非常多设计的推敲和取舍,
这些推敲和取舍是帮助我们理解计算机系统的极佳案例.</li>
</ul></div></div>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="risc-v书籍推荐"><i class="fa fa-comment-o"></i> RISC-V书籍推荐</h5></div><div class="panel-body"><p>除了RISC-V的官方手册之外, 图灵奖得主, 体系结构领域的一代宗师David Patterson教授
还为RISC-V的推广编写了一本入门书籍<a href="http://www.riscvbook.com" target="_blank">The RISC-V Reader</a>,
书中从系统的角度叙述了RISC-V大量的设计原则, 并与现有指令集进行对比, 非常值得一读.</p><p>2018年, 中科院计算所的三位研究生为这本书编写了第一版中文译本(其中一位也算是你们的直系师兄了);
2023年, 在出版社的支持下推出了第二版中文译本,
包含<a href="https://item.jd.com/14304044.html" target="_blank">纸质版</a>和<a href="https://ysyx.oscc.cc/books/riscv-reader.html" target="_blank">电子版</a>,
并将中文译本的书名取名为《RISC-V开放架构设计之道》, 大幅提升了阅读体验.</p><p>不过由于这本书并没有及时跟进RISC-V官方手册的最新内容,
如果你需要了解最新的RISC-V技术动向, 我们还是建议你阅读RISC-V的官方手册.</p></div></div>
<h2 id="rtfsc2">RTFSC(2)</h2>
<p>理解了上一小节的YEMU如何执行指令之后, 你就会对模拟器的框架有一个基本的认识了.
NEMU要模拟一个真实的ISA, 因此代码要比YEMU复杂得多, 但其中蕴含的基本原理是和YEMU相同的.
下面我们来介绍NEMU的框架代码如何实现指令的执行.</p>
<p>在RTFSC的过程中, 你会遇到用于抽象ISA差异的大部分API,
因此我们建议你先阅读<a href="nemu-isa-api.html">这个页面</a>来对这些API的功能进行基本的了解,
将来在代码中遇到它们的时候可以进行查阅.</p>
<p>我们在PA1中提到:</p>
<blockquote>
<p><code>cpu_exec()</code>又会调用<code>execute()</code>, 后者模拟了CPU的工作方式: 不断执行指令.
具体地, 代码将在一个for循环中不断调用<code>exec_once()</code>函数,
这个函数的功能就是我们在上一小节中介绍的内容: 让CPU执行当前PC指向的一条指令, 然后更新PC.</p>
</blockquote>
<p>具体地, <code>exec_once()</code>接受一个<code>Decode</code>类型的结构体指针<code>s</code>,
这个结构体用于存放在执行一条指令过程中所需的信息, 包括指令的PC, 下一条指令的PC等.
还有一些信息是ISA相关的, NEMU用一个结构类型<code>ISADecodeInfo</code>来对这些信息进行抽象,
具体的定义在<code>nemu/src/isa/$ISA/include/isa-def.h</code>中.
<code>exec_once()</code>会先把当前的PC保存到<code>s</code>的成员<code>pc</code>和<code>snpc</code>中,
其中<code>s->pc</code>就是当前指令的PC, 而<code>s->snpc</code>则是下一条指令的PC,
这里的<code>snpc</code>是"static next PC"的意思.</p>
<p>然后代码会调用<code>isa_exec_once()</code>函数(在<code>nemu/src/isa/$ISA/inst.c</code>中定义),
这是因为执行指令的具体过程是和ISA相关的, 在这里我们先不深究<code>isa_exec_once()</code>的细节.
但可以说明的是, 它会随着取指的过程修改<code>s->snpc</code>的值,
使得从<code>isa_exec_once()</code>返回后<code>s->snpc</code>正好为下一条指令的PC.
接下来代码将会通过<code>s->dnpc</code>来更新PC, 这里的<code>dnpc</code>是"dynamic next PC"的意思.
关于<code>snpc</code>和<code>dnpc</code>的区别, 我们会在下文进行说明.</p>
<p>忽略<code>exec_once()</code>中剩下与trace相关的代码, 我们就返回到<code>execute()</code>中.
代码会对一个用于记录客户指令的计数器加1, 然后进行一些trace和difftest相关的操作(此时先忽略),
然后检查NEMU的状态是否为<code>NEMU_RUNNING</code>, 若是, 则继续执行下一条指令, 否则则退出执行指令的循环.</p>
<p>事实上, <code>exec_once()</code>函数覆盖了指令周期的所有阶段: 取指, 译码, 执行, 更新PC,
接下来我们来看看NEMU是如何实现指令周期的每一个阶段的.</p>
<h3 id="取指instruction-fetch-if">取指(instruction fetch, IF)</h3>
<p><code>isa_exec_once()</code>做的第一件事情就是取指令.
在NEMU中, 有一个函数<code>inst_fetch()</code>(在<code>nemu/include/cpu/ifetch.h</code>中定义)专门负责取指令的工作.
<code>inst_fetch()</code>最终会根据参数<code>len</code>来调用<code>vaddr_ifetch()</code>(在<code>nemu/src/memory/vaddr.c</code>中定义),
而目前<code>vaddr_ifetch()</code>又会通过<code>paddr_read()</code>来访问物理内存中的内容.
因此, 取指操作的本质只不过就是一次内存的访问而已.</p>
<p><code>isa_exec_once()</code>在调用<code>inst_fetch()</code>的时候传入了<code>s->snpc</code>的地址,
因此<code>inst_fetch()</code>最后还会根据<code>len</code>来更新<code>s->snpc</code>, 从而让<code>s->snpc</code>指向下一条指令.</p>
<h3 id="译码instruction-decode-id">译码(instruction decode, ID)</h3>
<p>接下来代码会进入<code>decode_exec()</code>函数, 它首先进行的是译码相关的操作.
译码的目的是得到指令的操作和操作对象, 这主要是通过查看指令的<code>opcode</code>来决定的.
不同ISA的<code>opcode</code>会出现在指令的不同位置, 我们只需要根据指令的编码格式,
从取出的指令中识别出相应的<code>opcode</code>即可.</p>
<p>和YEMU相比, NEMU使用一种抽象层次更高的译码方式: 模式匹配,
NEMU可以通过一个模式字符串来指定指令中<code>opcode</code>, 例如在riscv32中有如下模式:</p>
<pre><code class="lang-c">INSTPAT_START();
INSTPAT(<span class="hljs-string">"??????? ????? ????? ??? ????? 00101 11"</span>, auipc, U, R(rd) = s->pc + imm);
<span class="hljs-comment">// ...</span>
INSTPAT_END();
</code></pre>
<p>其中<code>INSTPAT</code>(意思是instruction pattern)是一个宏(在<code>nemu/include/cpu/decode.h</code>中定义),
它用于定义一条模式匹配规则. 其格式如下:</p>
<pre><code>INSTPAT(模式字符串, 指令名称, 指令类型, 指令执行操作);
</code></pre><p><code>模式字符串</code>中只允许出现4种字符:</p>
<ul>
<li><code>0</code>表示相应的位只能匹配<code>0</code></li>
<li><code>1</code>表示相应的位只能匹配<code>1</code></li>
<li><code>?</code>表示相应的位可以匹配<code>0</code>或<code>1</code></li>
<li>空格是分隔符, 只用于提升模式字符串的可读性, 不参与匹配</li>
</ul>
<p><code>指令名称</code>在代码中仅当注释使用, 不参与宏展开;
<code>指令类型</code>用于后续译码过程; 而<code>指令执行操作</code>则是通过C代码来模拟指令执行的真正行为.</p>
<p>此外, <code>nemu/include/cpu/decode.h</code>中还定义了宏<code>INSTPAT_START</code>和<code>INSTPAT_END</code>.
<code>INSTPAT</code>又使用了另外两个宏<code>INSTPAT_INST</code>和<code>INSTPAT_MATCH</code>, 它们在<code>nemu/src/isa/$ISA/inst.c</code>中定义.
对上述代码进行宏展开并简单整理代码之后, 最后将会得到:</p>
<pre><code class="lang-c">{ <span class="hljs-keyword">const</span> <span class="hljs-keyword">void</span> * __instpat_end = &&__instpat_end_;
<span class="hljs-keyword">do</span> {
<span class="hljs-keyword">uint64_t</span> key, mask, shift;
pattern_decode(<span class="hljs-string">"??????? ????? ????? ??? ????? 00101 11"</span>, <span class="hljs-number">38</span>, &key, &mask, &shift);
<span class="hljs-keyword">if</span> ((((<span class="hljs-keyword">uint64_t</span>)s->isa.inst >> shift) & mask) == key) {
{
<span class="hljs-keyword">int</span> rd = <span class="hljs-number">0</span>;
<span class="hljs-keyword">word_t</span> src1 = <span class="hljs-number">0</span>, src2 = <span class="hljs-number">0</span>, imm = <span class="hljs-number">0</span>;
decode_operand(s, &rd, &src1, &src2, &imm, TYPE_U);
R(rd) = s->pc + imm;
}
<span class="hljs-keyword">goto</span> *(__instpat_end);
}
} <span class="hljs-keyword">while</span> (<span class="hljs-number">0</span>);
<span class="hljs-comment">// ...</span>
__instpat_end_: ; }
</code></pre>
<p>上述代码中的<code>&&__instpat_end_</code>使用了GCC提供的<a href="https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html" target="_blank">标签地址</a>扩展功能,
<code>goto</code>语句将会跳转到最后的<code>__instpat_end_</code>标签.
此外, <code>pattern_decode()</code>函数在<code>nemu/include/cpu/decode.h</code>中定义,
它用于将模式字符串转换成3个整型变量.</p>
<p><code>pattern_decode()</code>函数将模式字符串中的<code>0</code>和<code>1</code>抽取到整型变量<code>key</code>中,
<code>mask</code>表示<code>key</code>的掩码, 而<code>shift</code>则表示<code>opcode</code>距离最低位的比特数量, 用于帮助编译器进行优化.
具体地, 上述例子中:</p>
<pre><code class="lang-c">key = <span class="hljs-number">0x17</span>;
mask = <span class="hljs-number">0x7f</span>;
shift = <span class="hljs-number">0</span>;
</code></pre>
<p>考虑PA1中介绍的内建客户程序中的如下指令:</p>
<pre><code>0x00000297 auipc t0,0
</code></pre><p>NEMU取指令的时候会把指令记录到<code>s->isa.inst</code>中, 此时指令满足上述宏展开的<code>if</code>语句,
表示匹配到<code>auipc</code>指令的编码, 因此将会进行进一步的译码操作.</p>
<p>刚才我们只知道了指令的具体操作(比如<code>auipc</code>是将当前PC值与立即数相加并写入寄存器),
但我们还是不知道操作对象(比如立即数是多少, 写入到哪个寄存器).
为了解决这个问题, 代码需要进行进一步的译码工作,
这是通过调用<code>decode_operand()</code>函数来完成的.
这个函数将会根据传入的指令类型<code>type</code>来进行操作数的译码,
译码结果将记录到函数参数<code>rd</code>, <code>src1</code>, <code>src2</code>和<code>imm</code>中,
它们分别代表目的操作数的寄存器号码, 两个源操作数和立即数.</p>
<p>我们会发现, 类似寄存器和立即数这些操作数, 其实是非常常见的操作数类型.
为了进一步实现操作数译码和指令译码的解耦, 我们对这些操作数的译码进行了抽象封装:</p>
<ul>
<li>框架代码定义了<code>src1R()</code>和<code>src2R()</code>两个辅助宏, 用于寄存器的读取结果记录到相应的操作数变量中</li>
<li>框架代码还定义了<code>immI</code>等辅助宏, 用于从指令中抽取出立即数</li>
</ul>
<p>有了这些辅助宏, 我们就可以用它们来方便地编写<code>decode_operand()</code>了,
例如RISC-V中I型指令的译码过程可以通过如下代码实现:</p>
<pre><code class="lang-c"><span class="hljs-keyword">case</span> TYPE_I: src1R(); immI(); <span class="hljs-keyword">break</span>;
</code></pre>
<p>另外补充几点说明:</p>
<ul>
<li><code>decode_operand</code>中用到了宏<code>BITS</code>和<code>SEXT</code>, 它们均在<code>nemu/include/macro.h</code>中定义,
分别用于位抽取和符号扩展</li>
<li><code>decode_operand</code>会首先统一对目标操作数进行寄存器操作数的译码, 即调用<code>*rd = BITS(i, 11, 7)</code>,
不同的指令类型可以视情况使用<code>rd</code></li>
<li>在模式匹配过程的最后有一条<code>inv</code>的规则, 表示"若前面所有的模式匹配规则都无法成功匹配,
则将该指令视为非法指令</li>
</ul>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="x86的变长指令"><i class="fa fa-comment-o"></i> x86的变长指令</h5></div><div class="panel-body"><p>由于CISC指令变长的特性, x86指令长度和指令形式需要一边取指一边译码来确定,
而不像RISC指令集那样可以泾渭分明地处理取指和译码阶段,
因此你会在x86的译码过程中看到<code>inst_fetch()</code>的操作.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="立即数背后的故事"><i class="fa fa-question-circle"></i> 立即数背后的故事</h5></div><div class="panel-body"><p>框架代码通过<code>inst_fetch()</code>函数进行取指, 别看这里就这么一行代码,
其实背后隐藏着针对<a href="http://en.wikipedia.org/wiki/Endianness" target="_blank">字节序</a>的慎重考虑.
大部分同学的主机都是x86小端机, 当你使用高级语言或者汇编语言写了一个32位常数<code>0x1234</code>的时候,
在生成的二进制代码中, 这个常数对应的字节序列如下(假设这个常数在内存中的起始地址是x):</p><pre><code>x x+1 x+2 x+3
+----+----+----+----+
| 34 | 12 | 00 | 00 |
+----+----+----+----+
</code></pre><p>而大多数PC机都是小端架构(我们相信没有同学会使用IBM大型机来做PA), 当NEMU运行的时候,</p><pre><code class="lang-c">imm = inst_fetch(pc, <span class="hljs-number">4</span>);
</code></pre><p>这行代码会将<code>34 12 00 00</code>这个字节序列原封不动地从内存读入<code>imm</code>变量中,
主机的CPU会按照小端方式来解释这一字节序列, 于是会得到<code>0x1234</code>, 符合我们的预期结果.</p><p>Motorola 68k系列的处理器都是大端架构的.
现在问题来了, 考虑以下两种情况:</p><ul>
<li>假设我们需要将NEMU运行在Motorola 68k的机器上(把NEMU的源代码编译成Motorola 68k的机器码)</li>
<li>假设我们需要把Motorola 68k作为一个新的ISA加入到NEMU中</li>
</ul><p>在这两种情况下, 你需要注意些什么问题?
为什么会产生这些问题? 怎么解决它们?</p><p>事实上不仅仅是立即数的访问, 长度大于1字节的内存访问都需要考虑类似的问题.
我们在这里把问题统一抛出来, 以后就不再单独讨论了.</p></div></div>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="立即数背后的故事2"><i class="fa fa-question-circle"></i> 立即数背后的故事(2)</h5></div><div class="panel-body"><p>mips32和riscv32的指令长度只有32位, 因此它们不能像x86那样,
把C代码中的32位常数直接编码到一条指令中.
思考一下, mips32和riscv32应该如何解决这个问题?</p></div></div>
<!-- -->
<div class="panel panel-success"><div class="panel-heading"><h5 class="panel-title" id="我要被宏定义绕晕了-怎么办"><i class="fa fa-lightbulb-o"></i> 我要被宏定义绕晕了, 怎么办?</h5></div><div class="panel-body"><p>为了理解一个宏的语义, 你可能会尝试手动对它进行宏展开, 但你可能会碰到如下困难:</p><ul>
<li>宏嵌套的次数越多, 理解越困难</li>
<li>一些拼接宏会影响编辑器的代码跳转功能</li>
</ul><p>事实上, 为了进行宏展开, 你并不需要手动去进行操作, 因为肯定有工具能做这件事:
我们只需要让GCC把编译预处理的结果输出出来, 就可以看到宏展开的结果了.
有了宏展开的结果, 你就可以快速理解展开之后的语义,
然后反过来理解相应的宏是如何一步步被展开的了.</p><p>当然, 最方便的做法是让GCC编译NEMU的时候顺便输出预处理的结果,
如果你对Makefile的组织有一定的认识, 这件事当然也难不倒你了.</p></div></div>
<h3 id="执行execute-ex">执行(execute, EX)</h3>
<p>译码阶段结束之后, 代码将会执行模式匹配规则中指定的<code>指令执行操作</code>,
这部分操作会用到译码的结果, 并通过C代码来模拟指令执行的真正行为.
例如对于<code>auipc</code>指令, 由于译码阶段已经把U型立即数记录到操作数<code>imm</code>中了,
我们只需要通过<code>R(rd) = s->pc + imm</code>将立即数与当前PC值相加并写入目标寄存器中, 这样就完成了指令的执行.</p>
<p>指令执行的阶段结束之后, <code>decode_exec()</code>函数将会返回<code>0</code>, 并一路返回到<code>exec_once()</code>函数中.
不过目前代码并没有使用这个返回值, 因此可以忽略它.</p>
<h3 id="更新pc">更新PC</h3>
<p>最后是更新PC. 更新PC的操作非常简单, 只需要把<code>s->dnpc</code>赋值给<code>cpu.pc</code>即可.
我们之前提到了<code>snpc</code>和<code>dnpc</code>, 现在来说明一下它们的区别.</p>
<div class="panel panel-success"><div class="panel-heading"><h5 class="panel-title" id="静态指令和动态指令"><i class="fa fa-lightbulb-o"></i> 静态指令和动态指令</h5></div><div class="panel-body"><p>在程序分析领域中, 静态指令是指程序代码中的指令, 动态指令是指程序运行过程中的指令.
例如对于以下指令序列</p><pre><code>100: jmp 102
101: add
102: xor
</code></pre><p><code>jmp</code>指令的下一条静态指令是<code>add</code>指令, 而下一条动态指令则是<code>xor</code>指令.</p></div></div>
<p>有了静态指令和动态指令这两个概念之后, 我们就可以说明<code>snpc</code>和<code>dnpc</code>的区别了:
<code>snpc</code>是下一条静态指令, 而<code>dnpc</code>是下一条动态指令.
对于顺序执行的指令, 它们的<code>snpc</code>和<code>dnpc</code>是一样的;
但对于跳转指令, <code>snpc</code>和<code>dnpc</code>就会有所不同, <code>dnpc</code>应该指向跳转目标的指令.
显然, 我们应该使用<code>s->dnpc</code>来更新PC, 并且在指令执行的过程中正确地维护<code>s->dnpc</code>.</p>
<hr>
<p>上文已经把一条指令在NEMU中执行的流程进行了大概的介绍,
但还有少量的细节没有完全覆盖(例如x86的指令组译码表), 这些细节就交给你来去尝试理解啦.
不过为了特别照顾选择x86的同学, 我们还是准备了<a href="exec.html">一个例子</a>来RTFSC.</p>
<div class="panel panel-danger"><div class="panel-heading"><h5 class="panel-title" id="驾驭项目-而不是被项目驾驭"><i class="fa fa-bullhorn"></i> 驾驭项目, 而不是被项目驾驭</h5></div><div class="panel-body"><p>你和一个项目的关系会经历4个阶段:</p><ol>
<li>被驾驭: 你对它一无所知</li>
<li>一知半解: 你对其中的主要模块和功能有了基本的了解</li>
<li>驾轻就熟: 你对整个项目的细节都了如指掌</li>
<li>为你所用: 你可以随心所欲地在项目中添加你认为有用的功能</li>
</ol><p>在PA中, 达到第二个阶段的主要手段是阅读讲义和代码,
达到第三个阶段的主要手段是独立完成实验内容和独立调试.
至于要达到第四个阶段, 就要靠你的主观能动性了: 代码还有哪里做得不够好?
怎么样才算是够好? 应该怎么做才能达到这个目标?</p><p>你毕业后到了工业界或学术界, 就会发现真实的项目也都是这样:</p><ol>
<li>刚接触一个新项目, 不知道如何下手</li>
<li>RTFM, RTFSC, 大致明白项目组织结构和基本的工作流程</li>
<li>运行项目的时候发现有非预期行为(可能是配置错误或环境错误,
可能是和已有项目对接出错, 也可能是项目自身的bug), 然后调试.
在调试过程中, 对这些模块的理解会逐渐变得清晰.</li>
<li>哪天需要你在项目中添加一个新功能, 你会发现自己其实可以胜任.</li>
</ol><p>这说明了: <font color="red">如果你一遇到bug就找大神帮你调试,
你失去的机会和能力会比你想象的多得多</font>.</p></div></div>
<!--
> #### comment::拦截客户程序访存越界的非法行为
> 你将来很可能会遇到客户程序访存越界的错误, NEMU的框架代码一旦检测到这一行为就会直接panic.
> 这一行为的检测已经极大地帮助你发现代码的问题了,
> 想象一下, 如果NEMU并未拦截这一error, 你可能会看到怎么样的failure?
-->
<h3 id="结构化程序设计">结构化程序设计</h3>
<p>我们刚才介绍了译码过程中的一些辅助用的函数和宏, 它们的引入都是为了实现代码的解偶, 提升可维护性.
如果指令集越复杂, 指令之间的共性特征就越多, 以x86为例:</p>
<ul>
<li>对于同一条指令的不同形式, 它们的执行阶段是相同的.
例如<code>add_I2E</code>和<code>add_E2G</code>等, 它们的执行阶段都是把两个操作数相加, 把结果存入目的操作数.</li>