-
Notifications
You must be signed in to change notification settings - Fork 154
/
Copy path3.2.html
1269 lines (753 loc) · 99.2 KB
/
3.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>穿越时空的旅程 · 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="3.3.html" />
<link rel="prev" href="3.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 " 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 active" 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="." >穿越时空的旅程</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="异常响应机制">异常响应机制</h2>
<p>有了强大的硬件保护机制, 用户程序将无法把执行流切换到操作系统的任意代码了.
但为了实现最简单的操作系统, 硬件还需要提供一种可以限制入口的执行流切换方式.
这种方式就是自陷指令, 程序执行自陷指令之后, 就会陷入到操作系统预先设置好的跳转目标.
这个跳转目标也称为异常入口地址.</p>
<p>这一过程是ISA规范的一部分, 称为中断/异常响应机制.
大部分ISA并不区分CPU的异常和自陷, 甚至是将会在PA4最后介绍的硬件中断, 而是对它们进行统一的响应.
目前我们并未加入硬件中断, 因此先把这个机制简称为"异常响应机制"吧.</p>
<h3 id="x86">x86</h3>
<p>x86提供<code>int</code>指令作为自陷指令, 但其异常响应机制和其它ISA相比会复杂一些.
在x86中, 上述的异常入口地址是通过门描述符(Gate Descriptor)来指示的.
门描述符是一个8字节的结构体, 里面包含着不少细节的信息,
我们在NEMU中简化了门描述符的结构, 只保留存在位P和偏移量OFFSET:</p>
<pre><code> 31 23 15 7 0
+-----------------+-----------------+---+-------------------------------+
| OFFSET 31..16 | P | Don't care |4
+-----------------------------------+---+-------------------------------+
| Don't care | OFFSET 15..0 |0
+-----------------+-----------------+-----------------+-----------------+
</code></pre><p>P位来用表示这一个门描述符是否有效, OFFSET用来指示异常入口地址.
有了门描述符, 用户程序就只能跳转到门描述符中OFFSET所指定的位置,
再也不能随心所欲地跳转到操作系统的任意代码了.</p>
<p>为了方便管理各个门描述符, x86把内存中的某一段数据专门解释成一个数组,
叫IDT(Interrupt Descriptor Table, 中断描述符表), 数组的一个元素就是一个门描述符.
为了从数组中找到一个门描述符, 我们还需要一个索引.
对于CPU异常来说, 这个索引由CPU内部产生(例如除零异常为0号异常),或者由<code>int</code>指令给出(例如<code>int $0x80</code>).
最后, 为了在内存中找到IDT, x86使用IDTR寄存器来存放IDT的首地址和长度.
操作系统的代码事先把IDT准备好, 然后执行一条特殊的指令<code>lidt</code>,
来在IDTR中设置好IDT的首地址和长度, 这一异常响应机制就可以正常工作了.
现在是万事俱备, 等到程序执行自陷指令或者触发异常的时候, CPU就会按照设定好的IDT跳转到异常入口地址:</p>
<pre><code> | |
| Entry Point |<----+
| | |
| | |
| | |
+---------------+ |
| | |
| | |
| | |
+---------------+ |
|offset | | |
|-------+-------| |
| | offset|-----+
index--->+---------------+
| |
|Gate Descriptor|
| |
IDT--->+---------------+
| |
| |
</code></pre><p>不过, 我们将来还是有可能需要返回到程序的当前状态来继续执行的,
比如通过<code>int3</code>触发的断点异常.
这意味着, 我们需要在进行响应异常的时候保存好程序当前的状态.
于是, 触发异常后硬件的响应过程如下:</p>
<ol>
<li>从IDTR中读出IDT的首地址</li>
<li>根据异常号在IDT中进行索引, 找到一个门描述符</li>
<li>将门描述符中的offset域组合成异常入口地址</li>
<li>依次将eflags, cs(代码段寄存器), eip(也就是PC)寄存器的值压栈</li>
<li>跳转到异常入口地址</li>
</ol>
<p>在计算机和谐社会中, 大部分门描述符都不能让用户进程随意使用,
否则恶意程序就可以通过<code>int</code>指令欺骗操作系统.
例如恶意程序执行<code>int $0x2</code>来谎报电源掉电, 扰乱其它进程的正常运行.
因此执行<code>int</code>指令也需要进行特权级检查, 但PA中就不实现这一保护机制了,
具体的检查规则我们也就不展开讨论了, 需要了解时RTFM即可.</p>
<h3 id="mips32">mips32</h3>
<p>mips32提供<code>syscall</code>指令作为自陷指令, 它的工作过程十分简单.
mips32约定, 上述的异常入口地址总是<code>0x80000180</code>.
为了保存程序当前的状态, mips32提供了一些特殊的系统寄存器,
这些寄存器位于0号协处理器(Co-Processor 0)中, 因此也称CP0寄存器.
在PA中, 我们只使用如下3个CP0寄存器:</p>
<ul>
<li>epc寄存器 - 存放触发异常的PC</li>
<li>status寄存器 - 存放处理器的状态</li>
<li>cause寄存器 - 存放触发异常的原因</li>
</ul>
<p>mips32触发异常后硬件的响应过程如下:</p>
<ol>
<li>将当前PC值保存到epc寄存器</li>
<li>在cause寄存器中设置异常号</li>
<li>在status寄存器中设置异常标志, 使处理器进入内核态</li>
<li>跳转到<code>0x80000180</code></li>
</ol>
<h3 id="riscv32">riscv32</h3>
<p>riscv32提供<code>ecall</code>指令作为自陷指令, 并提供一个mtvec寄存器来存放异常入口地址.
为了保存程序当前的状态, riscv32提供了一些特殊的系统寄存器, 叫控制状态寄存器(CSR寄存器).
在PA中, 我们只使用如下3个CSR寄存器:</p>
<ul>
<li>mepc寄存器 - 存放触发异常的PC</li>
<li>mstatus寄存器 - 存放处理器的状态</li>
<li>mcause寄存器 - 存放触发异常的原因</li>
</ul>
<p>riscv32触发异常后硬件的响应过程如下:</p>
<ol>
<li>将当前PC值保存到mepc寄存器</li>
<li>在mcause寄存器中设置异常号</li>
<li>从mtvec寄存器中取出异常入口地址</li>
<li>跳转到异常入口地址</li>
</ol>
<hr>
<p>需要注意的是, 上述保存程序状态以及跳转到异常入口地址的工作,
都是硬件自动完成的, 不需要程序员编写指令来完成相应的内容.
事实上, 这只是一个简化后的过程, 在真实的计算机上还要处理很多细节问题,
比如x86和riscv32的特权级切换等, 在这里我们就不深究了.
ISA手册中还记录了处理器对中断号和异常号的分配情况,
并列出了各种异常的详细解释, 需要了解的时候可以进行查阅.</p>
<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>这些程序状态(x86的eflags, cs, eip; mips32的epc, status, cause;
riscv32的mepc, mstatus, mcause)必须由硬件来保存吗? 能否通过软件来保存? 为什么?</p></div></div>
<p>由于异常入口地址是硬件和操作系统约定好的, 接下来的处理过程将会由操作系统来接管,
操作系统将视情况决定是否终止当前程序的运行(例如触发段错误的程序将会被杀死).
若决定无需杀死当前程序, 等到异常处理结束之后, 就根据之前保存的信息恢复程序的状态,
并从异常处理过程中返回到程序触发异常之前的状态.
具体地:</p>
<ul>
<li>x86通过<code>iret</code>指令从异常处理过程中返回,
它将栈顶的三个元素来依次解释成eip, cs, eflags, 并恢复它们.</li>
<li>mips32通过<code>eret</code>指令从异常处理过程中返回,
它将清除status寄存器中的异常标志, 并根据epc寄存器恢复PC.</li>
<li>riscv32通过<code>mret</code>指令从异常处理过程中返回, 它将根据mepc寄存器恢复PC.</li>
</ul>
<h3 id="状态机视角下的异常响应机制">状态机视角下的异常响应机制</h3>
<p>程序是个<code>S = <R, M></code>的状态机, 我们之前已经讨论过在TRM和IOE中这个状态机的具体行为.
如果要给计算机添加异常响应机制, 我们又应该如何对这个状态机进行扩充呢?</p>
<p>首先当然是对<code>R</code>的扩充, 除了PC和通用寄存器之外, 还需要添加上文提到的一些特殊寄存器.
我们不妨把这些寄存器称为系统寄存器(System Register),
因为这些寄存器的作用都是和系统功能相关的, 平时进行计算的时候不会使用.
扩充之后的寄存器可以表示为<code>R = {GPR, PC, SR}</code>.
异常响应机制和内存无关, 因此我们无需对<code>M</code>的含义进行修改.</p>
<p>对状态转移的扩充就比较有趣了. 我们之前都是认为程序执行的每一条指令都会成功,
从而状态机会根据指令的语义进行状态转移.
添加异常响应机制之后, 我们允许一条指令的执行会"失败".
为了描述指令执行失败的行为, 我们可以假设CPU有一条虚构的指令<code>raise_intr</code>,
执行这条虚构指令的行为就是上文提到的异常响应过程.
显然, 这一行为是可以用状态机视角来描述的, 例如在riscv32中可以表示成:</p>
<pre><code>SR[mepc] <- PC
SR[mcause] <- 一个描述失败原因的号码
PC <- SR[mtvec]
</code></pre><p>有了这条虚构的指令, 我们就可以从状态机视角来理解异常响应的行为了:
如果一条指令执行成功, 其行为和之前介绍的TRM与IOE相同;
如果一条指令执行失败, 其行为等价于执行了虚构的<code>raise_intr</code>指令.</p>
<p>那么, "一条指令的执行是否会失败"这件事是不是确定性的呢?
显然这取决于"失败"的定义, 例如除0就是"除法指令的第二个操作数为0",
非法指令可以定义成"不属于ISA手册描述范围的指令",
而自陷指令可以认为是一种特殊的无条件失败.
不同的ISA手册都有各自对"失败"的定义, 例如RISC-V手册就不认为除0是一种失败,
因此即使除数为0, 在RISC-V处理器中这条指令也会按照指令手册的描述来执行.</p>
<p>事实上, 我们可以把这些失败的条件表示成一个函数<code>fex: S -> {0, 1}</code>,
给定状态机的任意状态<code>S</code>, <code>fex(S)</code>都可以唯一表示当前PC指向的指令是否可以成功执行.
于是, 给计算机加入异常响应机制并不会增加系统的不确定性,
这大大降低了我们理解异常响应机制的难度, 同时也让调试不至于太困难:
一个程序运行多次, 还是会在相同的地方抛出相同的异常, 从而进行相同的状态转移
(IOE的输入指令会引入一些不确定性, 但目前还是在我们能控制的范围内).</p>
<p><img src="images/cte.png" alt="cte"></p>
<p>最后, 异常响应机制的加入还伴随着一些系统指令的添加,
例如x86的<code>lidt</code>, <code>iret</code>, riscv32的<code>csrrw</code>, <code>mret</code>等.
这些指令除了用于专门对状态机中的<code>SR</code>进行操作之外, 它们本质上和TRM的计算指令没有太大区别,
因此它们的行为也不难理解.</p>
<h2 id="将上下文管理抽象成cte">将上下文管理抽象成CTE</h2>
<p>我们刚才提到了程序的状态, 在操作系统中有一个等价的术语, 叫"上下文".
因此, 硬件提供的上述在操作系统和用户程序之间切换执行流的功能,
在操作系统看来, 都可以划入上下文管理的一部分.</p>
<p>与IOE一样, 上下文管理的具体实现也是架构相关的:
例如上文提到, x86/mips32/riscv32中分别通过<code>int</code>/<code>syscall</code>/<code>ecall</code>指令来进行自陷,
<code>native</code>中甚至可以通过一些神奇的库函数来模拟相应的功能;
而上下文的具体内容, 在不同的架构上也显然不一样(比如寄存器就已经不一样了).
于是, 我们可以将上下文管理的功能划入到AM的一类新的API中, 名字叫CTE(ConText Extension).</p>
<p>接下来的问题是, 如何将不同架构的上下文管理功能抽象成统一的API呢?
换句话说, 我们需要思考, 操作系统的处理过程其实需要哪些信息?</p>
<ul>
<li>首先当然是引发这次执行流切换的原因, 是程序除0, 非法指令, 还是触发断点,
又或者是程序自愿陷入操作系统? 根据不同的原因, 操作系统都会进行不同的处理.</li>
<li>然后就是程序的上下文了, 在处理过程中, 操作系统可能会读出上下文中的一些寄存器,
根据它们的信息来进行进一步的处理.
例如操作系统读出PC所指向的非法指令, 看看其是否能被模拟执行.
事实上, 通过这些上下文, 操作系统还能实现一些神奇的功能, 你将会在PA4中了解更详细的信息.</li>
</ul>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="用软件模拟指令"><i class="fa fa-comment-o"></i> 用软件模拟指令</h5></div><div class="panel-body"><p>在一些嵌入式场景中, 处理器对低功耗的要求非常严格, 很多时候都会去掉浮点处理单元FPU来节省功耗.
这时候如果软件要执行一条浮点指令, 处理器就会抛出一个非法指令的异常.
有了异常响应机制, 我们就可以在异常处理的过程中模拟这条非法指令的执行了,
原理和PA2中的指令执行过程非常类似.
在不带FPU的各种处理器中, 都可以通过这种方式来执行浮点指令.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="在am中执行浮点指令是ub"><i class="fa fa-comment-o"></i> 在AM中执行浮点指令是UB</h5></div><div class="panel-body"><p>换句话说, AM的运行时环境不支持浮点数. 这听上去太暴力了.
之所以这样决定, 是因为IEEE 754是个工业级标准,
为了形式化逻辑上的soundness和completeness, 标准里面可能会有各种奇怪的设定,
例如不同的舍入方式, inf和nan的引入等等, 作为教学其实没有必要去理解它们的所有细节;
但如果要去实现一个正确的FPU, 你就没法摆脱这些细节了.</p><p>和PA2中的定点指令不同, 浮点指令在PA中用到的场合比较少, 而且我们有别的方式可以绕开,
所以就怎么简单怎么来了, 于是就UB吧.
当然, 如果你感兴趣, 你也可以考虑实现一个简化版的FPU.
毕竟是UB, 如果你的FPU行为正确, 也不算违反规定.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="另一个ub"><i class="fa fa-comment-o"></i> 另一个UB</h5></div><div class="panel-body"><p>另一种你可能会碰到的UB是栈溢出, 对, 就是stackoverflow的那个.
检测栈溢出需要一个更强大的运行时环境, AM肯定是无能为力了, 于是就UB吧.</p><p>不过, AM究竟给程序提供了多大的栈空间呢?
事实上, 如果你在PA2的时候尝试努力了解每一处细节, 你已经知道这个问题的答案了;
如果你没有, 你需要反思一下自己了, 还是认真RTFSC吧.</p></div></div>
<p>所以, 我们只要把这两点信息抽象成一种统一的表示方式, 就可以定义出CTE的API了.
对于切换原因, 我们只需要定义一种统一的描述方式即可.
CTE定义了名为"事件"的如下数据结构(见<code>abstract-machine/am/include/am.h</code>):</p>
<pre><code class="lang-c"><span class="hljs-keyword">typedef</span> <span class="hljs-keyword">struct</span> Event {
<span class="hljs-keyword">enum</span> { ... } event;
<span class="hljs-keyword">uintptr_t</span> cause, ref;
<span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *msg;
} Event;
</code></pre>
<p>其中<code>event</code>表示事件编号, <code>cause</code>和<code>ref</code>是一些描述事件的补充信息,
<code>msg</code>是事件信息字符串, 我们在PA中只会用到<code>event</code>.
然后, 我们只要定义一些统一的事件编号(上述枚举常量), 让每个架构在实现各自的CTE API时,
都统一通过上述结构体来描述执行流切换的原因, 就可以实现切换原因的抽象了.</p>
<p>对于上下文, 我们只能将描述上下文的结构体类型名统一成<code>Context</code>,
至于其中的具体内容, 就无法进一步进行抽象了.
这主要是因为不同架构之间上下文信息的差异过大, 比如mips32有32个通用寄存器,
就从这一点来看, mips32和x86的<code>Context</code>注定是无法抽象成完全统一的结构的.
所以在AM中, <code>Context</code>的具体成员也是由不同的架构自己定义的,
比如<code>x86-nemu</code>的<code>Context</code>结构体在<code>abstract-machine/am/include/arch/x86-nemu.h</code>中定义.
因此, 在操作系统中对<code>Context</code>成员的直接引用,
都属于架构相关的行为, 会损坏操作系统的可移植性.
不过大多数情况下, 操作系统并不需要单独访问<code>Context</code>结构中的成员.
CTE也提供了一些的接口, 来让操作系统在必要的时候访问它们,
从而保证操作系统的相关代码与架构无关.</p>
<p>最后还有另外两个统一的API:</p>
<ul>
<li><code>bool cte_init(Context* (*handler)(Event ev, Context *ctx))</code>用于进行CTE相关的初始化操作.
其中它还接受一个来自操作系统的事件处理回调函数的指针, 当发生事件时,
CTE将会把事件和相关的上下文作为参数, 来调用这个回调函数, 交由操作系统进行后续处理.</li>
<li><code>void yield()</code>用于进行自陷操作, 会触发一个编号为<code>EVENT_YIELD</code>事件.
不同的ISA会使用不同的自陷指令来触发自陷操作, 具体实现请RTFSC.</li>
</ul>
<p>CTE中还有其它的API, 目前不使用, 故暂不介绍它们.</p>
<p>需要注意的是, 上文介绍的异常和事件是两个层次的概念:
异常是处理器硬件层次的机制, 事件是AM对异常的一种封装.
因此, 自陷异常和自陷事件也是不同层次的概念, 异常号和事件编号也并不相同.</p>
<p>特别地, 为了简化ISA的设计, 处理器通常只会提供一条自陷指令,
软件层次上的多个事件可能都通过相同的自陷指令来实现, 因此CTE需要额外的方式区分它们.
如果自陷指令本身可以携带参数, 就可以用不同的参数指示不同的事件, 例如x86和mips32都可以采用这种方式;
如果自陷指令本身不能携带参数, 就需要通过其他状态来区分, 例如通过某个寄存器的值来区分, riscv32采用这种方式.
因此, 要识别一个事件, 不仅要看异常号, 有时候还需要考虑其他状态.</p>
<h2 id="触发第一个异常">触发第一个异常</h2>
<p>接下来, 我们尝试通过<code>am-tests</code>中的<code>yield test</code>测试触发一次自陷操作, 来梳理过程中的细节.
这个测试还支持时钟中断和外部中断, 但这还需要硬件提供中断相关的功能, 目前我们暂时不关心它们.</p>
<h3 id="设置异常入口地址">设置异常入口地址</h3>
<p>在触发自陷操作前, 首先需要按照ISA的约定来设置异常入口地址, 将来切换执行流时才能跳转到正确的异常入口.
这显然是架构相关的行为, 因此我们把这一行为放入CTE中, 而不是让<code>am-tests</code>直接来设置异常入口地址.
当我们选择<code>yield test</code>时, <code>am-tests</code>会通过<code>cte_init()</code>函数对CTE进行初始化, 其中包含一些简单的宏展开代码.
这最终会调用位于<code>abstract-machine/am/src/$ISA/nemu/cte.c</code>中的<code>cte_init()</code>函数.
<code>cte_init()</code>函数会做两件事情, 第一件就是设置异常入口地址:</p>