-
Notifications
You must be signed in to change notification settings - Fork 154
/
Copy path3.5.html
1493 lines (967 loc) · 150 KB
/
3.5.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="PA4.html" />
<link rel="prev" href="3.4.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 " 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 active" 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>
<h3 id="更丰富的运行时环境">更丰富的运行时环境</h3>
<p>我们已经通过系统调用和文件的形式向用户程序提供IOE的访问方式, 并通过NDL进行了一些底层的封装.
但对于一些较为复杂的程序, 直接使用NDL进行编程还是比较困难.
为了更好地支持这些复杂程序的开发和运行, 我们需要提供更高层次的库.</p>
<h4 id="多媒体库">多媒体库</h4>
<p>在Linux中, 有一批GUI程序是使用SDL库来开发的.
在Navy中有一个miniSDL库, 它可以提供一些兼容SDL的API,
这样这批GUI程序就可以很容易地移植到Navy中了.
miniSDL的代码位于<code>navy-apps/libs/libminiSDL/</code>目录下, 它由6个模块组成:</p>
<ul>
<li><code>timer.c</code>: 时钟管理</li>
<li><code>event.c</code>: 事件处理</li>
<li><code>video.c</code>: 绘图接口</li>
<li><code>file.c</code>: 文件抽象</li>
<li><code>audio.c</code>: 音频播放</li>
<li><code>general.c</code>: 常规功能, 包括初始化, 错误管理等</li>
</ul>
<p>我们可以通过NDL来支撑miniSDL的底层实现, 让miniSDL向用户程序提供更丰富的功能,
这样我们就可以在Navy上运行更复杂的程序了.
miniSDL中的API和SDL同名, 你可以通过<a href="https://www.libsdl.org/release/SDL-1.2.15/docs/" target="_blank">RTFM</a>来查阅这些API的具体行为.
另外miniSDL中的大部分API都没有实现, 你最好想个办法让程序用到某个未实现API的时候提醒你,
否则你可能难以理解由此导致的复杂程序非预期行为.</p>
<div class="panel panel-danger"><div class="panel-heading"><h5 class="panel-title" id="一定要通过rtfm了解sdl-api的行为"><i class="fa fa-exclamation"></i> 一定要通过RTFM了解SDL API的行为</h5></div><div class="panel-body"><p>我们在讲义中只会概括地介绍这些API的作用, 请你务必查阅SDL手册来理解它们的具体行为.</p></div></div>
<h4 id="定点算术">定点算术</h4>
<p>有一些程序的逻辑会使用实数, 目前真实的计算机系统一般都带有FPU,
因此开发者一般也会选择使用浮点数来表示这些实数.
但浮点数标准对一个面向教学的计算机系统来说实在太复杂了,
尤其是考虑到自制处理器的情况: 在硬件上实现一个正确的FPU对大家来说是一件非常困难的事情.
因此我们在Project-N的整个体系中都不打算引入浮点数:
NEMU没有FPU, 在AM中执行浮点操作是UB, Nanos-lite认为浮点寄存器不属于上下文的一部分,
Navy中也不提供浮点数相关的运行时环境(我们在编译Newlib的时候定义了宏<code>NO_FLOATING_POINT</code>).</p>
<p>如果我们能够用其它方式来实现程序的逻辑, 那么这些酷炫的程序都将有机会在你自己设计的处理器上运行.
事实上, 浮点数并不是实数的唯一表示, 用定点数也可以实现实数!
而且定点数的运算可以通过整数运算来实现, 这意味着,
我们可以通过整数运算指令来实现实数的逻辑, 而无需在硬件上引入FPU来运行这些程序.
这样的一个算术体系称为<a href="http://en.wikipedia.org/wiki/Fixed-point_arithmetic" target="_blank">定点算术</a>.</p>
<p>Navy中提供了一个fixedptc的库, 专门用于进行定点算术.
fixedptc库默认采用32位整数来表示实数, 其具体格式为"24.8"
(见<code>navy-apps/libs/libfixedptc/include/fixedptc.h</code>),
表示整数部分占24位, 小数部分占8位, 也可以认为实数的小数点总是固定位于第8位二进制数的左边.
库中定义了<code>fixedpt</code>的类型, 用于表示定点数, 可以看到它的本质是<code>int32_t</code>类型.</p>
<pre><code>31 30 8 0
+----+---------------------------+----------+
|sign| integer | fraction |
+----+---------------------------+----------+
</code></pre><p>这样, 对于一个实数<code>a</code>, 它的<code>fixedpt</code>类型表示<code>A = a * 2^8</code>(截断结果的小数部分).
例如实数<code>1.2</code>和<code>5.6</code>用<code>FLOAT</code>类型来近似表示, 就是</p>
<pre><code>1.2 * 2^8 = 307 = 0x133
+----+---------------------------+----------+
| 0 | 1 | 33 |
+----+---------------------------+----------+
5.6 * 2^8 = 1433 = 0x599
+----+---------------------------+----------+
| 0 | 5 | 99 |
+----+---------------------------+----------+
</code></pre><p>而实际上, 这两个<code>fixedpt</code>类型数据表示的实数(真值)是:</p>
<pre><code>0x133 / 2^8 = 1.19921875
0x599 / 2^8 = 5.59765625
</code></pre><p>对于负实数, 我们用相应正数的相反数来表示, 例如<code>-1.2</code>的<code>fixedpt</code>类型表示为:</p>
<pre><code>-(1.2 * 2^8) = -0x133 = 0xfffffecd
</code></pre><div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="比较fixedpt和float"><i class="fa fa-question-circle"></i> 比较fixedpt和float</h5></div><div class="panel-body"><p><code>fixedpt</code>和<code>float</code>类型的数据都是32位, 它们都可以表示2^32个不同的数.
但由于表示方法不一样, <code>fixedpt</code>和<code>float</code>能表示的数集是不一样的.
思考一下, 我们用<code>fixedpt</code>来模拟表示<code>float</code>, 这其中隐含着哪些取舍?</p></div></div>
<p>接下来我们来考虑<code>fixedpt</code>类型的常见运算, 假设实数<code>a</code>, <code>b</code>的<code>fixedpt</code>类型表示分别为<code>A</code>, <code>B</code>.</p>
<ul>
<li>由于我们使用整数来表示<code>fixedpt</code>类型, <code>fixedpt</code>类型的加法可以直接用整数加法来进行:<pre><code>A + B = a * 2^8 + b * 2^8 = (a + b) * 2^8
</code></pre></li>
<li>由于我们使用补码的方式来表示<code>fixedpt</code>类型数据, 因此<code>fixedpt</code>类型的减法可以用整数减法来进行.<pre><code>A - B = a * 2^8 - b * 2^8 = (a - b) * 2^8
</code></pre></li>
<li><code>fixedpt</code>类型的乘除法和加减法就不一样了:<pre><code>A * B = a * 2^8 * b * 2^8 = (a * b) * 2^16 != (a * b) * 2^8
</code></pre>也就是说, 直接把两个<code>fixedpt</code>数据相乘得到的结果并不等于相应的两个实数乘积的<code>fixedpt</code>表示.
为了得到正确的结果, 我们需要对相乘的结果进行调整: 只要将结果除以<code>2^8</code>, 就能得出正确的结果了.
除法也需要对结果进行调整, 至于如何调整, 当然难不倒聪明的你啦.</li>
<li>如果把<code>A = a * 2^8</code>看成一个映射, 那么在这个映射的作用下, 关系运算是保序的,
即<code>a <= b</code>当且仅当<code>A <= B</code>, 故<code>fixedpt</code>类型的关系运算都可以用整数的关系运算来进行.</li>
</ul>
<p>有了这些结论, 要用<code>fixedpt</code>类型来模拟实数运算就很方便了.
fixedptc库已经提供了一些常用的API, 例如</p>
<pre><code class="lang-c"><span class="hljs-keyword">float</span> a = <span class="hljs-number">1.2</span>;
<span class="hljs-keyword">float</span> b = <span class="hljs-number">10</span>;
<span class="hljs-keyword">int</span> c = <span class="hljs-number">0</span>;
<span class="hljs-keyword">if</span> (b > <span class="hljs-number">7.9</span>) {
c = (a + <span class="hljs-number">1</span>) * b / <span class="hljs-number">2.3</span>;
}
</code></pre>
<p>用<code>fixedpt</code>类型来表示就是</p>
<pre><code class="lang-c">fixedpt a = fixedpt_rconst(<span class="hljs-number">1.2</span>);
fixedpt b = fixedpt_fromint(<span class="hljs-number">10</span>);
<span class="hljs-keyword">int</span> c = <span class="hljs-number">0</span>;
<span class="hljs-keyword">if</span> (b > fixedpt_rconst(<span class="hljs-number">7.9</span>)) {
c = fixedpt_toint(fixedpt_div(fixedpt_mul(a + FIXEDPT_ONE, b), fixedpt_rconst(<span class="hljs-number">2.3</span>)));
}
</code></pre>
<p>可以看到, 我们只是把实数映射到定点算术体系, 在其中进行运算之后, 再映射回来.
如果我们最后需要的是一个整数(例如上述例子中的<code>c</code>),
那么我们就可以在不引入浮点指令的情况下实现程序原来的实数逻辑.</p>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="神奇的fixedptrconst"><i class="fa fa-question-circle"></i> 神奇的fixedpt_rconst</h5></div><div class="panel-body"><p>阅读<code>fixedpt_rconst()</code>的代码, 从表面上看, 它带有非常明显的浮点操作,
但从编译结果来看却没有任何浮点指令. 你知道其中的原因吗?</p></div></div>
<p>通过这种方法, 只要程序中使用的实数范围不是很大, 对计算精度的要求不是很高,
我们都可以将其中的浮点操作替换成定点操作, 来在避免引入浮点指令的情况下保持程序的基本逻辑.</p>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="实现更多的fixedptc-api"><i class="fa fa-edit"></i> 实现更多的fixedptc API</h5></div><div class="panel-body"><p>为了让大家更好地理解定点数的表示, 我们在<code>fixedptc.h</code>中去掉了一些API的实现, 你需要实现它们.
关于<code>fixedpt_floor()</code>和<code>fixedpt_ceil()</code>, 你需要严格按照<code>man</code>中<code>floor()</code>和<code>ceil()</code>的语义来实现它们,
否则在程序中用<code>fixedpt_floor()</code>代替<code>floor()</code>之后行为会产生差异,
在类似仙剑奇侠传这种规模较大的程序中, 这种差异导致的现象是非常难以理解的.
因此你也最好自己编写一些测试用例来测试你的实现.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="如何将浮点变量转换成fixedpt类型"><i class="fa fa-edit"></i> 如何将浮点变量转换成fixedpt类型?</h5></div><div class="panel-body"><p>假设有一个<code>void *p</code>的指针变量, 它指向了一个32位变量,
这个变量的本质是<code>float</code>类型, 它的真值落在<code>fixedpt</code>类型可表示的范围中.
如果我们定义一个新的函数<code>fixedpt fixedpt_fromfloat(void *p)</code>, 如何在不引入浮点指令的情况下实现它?</p></div></div>
<p>fixedptc库还提供了<code>sin</code>, <code>cos</code>, <code>exp</code>, <code>ln</code>等初等函数的定点算术实现, 这基本上可以满足大部分程序的需求.
但由于<code>fixedpt</code>的小数部分只有8位, 这些函数的计算精度可能会很低, 不过这对Navy上的程序来说已经足够了.</p>
<h4 id="navy作为基础设施">Navy作为基础设施</h4>
<p>我们在PA2中介绍了AM中的<code>native</code>架构, 借助AM API的抽象,
我们可以把自己写的程序先运行在<code>native</code>上, 这样就可以有效地把硬件(NEMU)bug和软件bug区分出来.
那么在Navy中, 我们能不能实现类似的效果呢?</p>
<p>答案是肯定的, 因为这样的效果就是计算机作为一个抽象层带给我们的礼物.
Navy提供的运行时环境包括libos, libc(Newlib), 一些特殊的文件, 以及各种面向应用程序的库.
我们把前三者称为"操作系统相关的运行时环境", 而面向应用程序的库与操作系统关系不大,
在这个问题的讨论中, 我们甚至可以将它们归到Navy应用程序的类别中.
类似在AM中用Linux native的功能实现AM的API,
我们也可以用Linux native的功能来实现上述运行时环境,
从而支撑相同的Navy应用程序的运行, 来单独对它们进行测试.
这样我们就实现了操作系统相关的运行时环境与Navy应用程序的解耦.</p>
<p>我们在Navy中提供了一个特殊的ISA叫<code>native</code>来实现上述的解耦, 它和其它ISA的不同之处在于:</p>
<ul>
<li>链接时绕开libos和Newlib, 让应用程序直接链接Linux的glibc</li>
<li>通过一些Linux native的机制实现<code>/dev/events</code>, <code>/dev/fb</code>等特殊文件的功能
(见<code>navy-apps/libs/libos/src/native.cpp</code>)</li>
<li>编译到Navy native的应用程序可以直接运行, 也可以用gdb来调试(见<code>navy-apps/scripts/native.mk</code>),
而编译到其它ISA的应用程序只能在Nanos-lite的支撑下运行</li>
</ul>
<p>虽然Navy的<code>native</code>和AM中的<code>native</code>同名, 但它们的机制是不同的:
在AM native上运行的系统, 需要AM, Nanos-lite, libos, libc这些抽象层来支撑上述的运行时环境,
在AM中的<code>ARCH=native</code>, 在Navy中对应的是<code>ISA=am_native</code>;
而在Navy native中, 上述运行时环境是直接由Linux native实现的.</p>
<p>你可以在<code>bmp-test</code>所在的目录下运行<code>make ISA=native run</code>,
来把<code>bmp-test</code>编译到Navy native上并直接运行, 还可以通过<code>make ISA=native gdb</code>对它进行调试.
这样你就可以在Linux native的环境下单独测试Navy中除了libos和Newlib之外的所有代码了(例如NDL和miniSDL).
一个例外是Navy中的dummy, 由于它通过<code>_syscall_()</code>直接触发系统调用,
这样的代码并不能直接在Linux native上直接运行, 因为Linux不存在这个系统调用(或者编号不同).</p>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="神奇的ldpreload"><i class="fa fa-question-circle"></i> 神奇的LD_PRELOAD</h5></div><div class="panel-body"><p><code>bmp-test</code>需要打开一个路径为<code>/share/pictures/projectn.bmp</code>的文件,
但在Linux native中, 这个路径对应的文件并不存在.
但我们还是把<code>bmp-test</code>成功运行起来了, 你知道这是如何实现的吗?
如果你感兴趣, 可以在互联网上搜索<code>LD_PRELOAD</code>相关的内容.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="wine-wsl和运行时环境兼容"><i class="fa fa-comment-o"></i> Wine, WSL和运行时环境兼容</h5></div><div class="panel-body"><p>我们可以通过Linux native来实现Navy的运行时环境, 从而可以让Navy中的应用程序在Linux native上运行.
那么我们能不能实现其它操作系统的运行时环境, 比如在Linux中提供一套兼容Windows的运行时环境,
从而支持Windows应用程序在Linux上的运行呢?</p><p><a href="https://www.winehq.org/" target="_blank">Wine</a>就是这样的一个项目, 它通过Linux的运行时环境实现Windows相关的API.
另一个方向相反的项目是<a href="https://docs.microsoft.com/en-us/windows/wsl/about" target="_blank">WSL</a>, 它是通过Windows的运行时环境来实现Linux的API,
从而支撑Linux程序在Windows上的运行, 不过WSL还修改了Windows内核, 让它对Linux程序提供专门的支持.
但完整的Linux和Windows运行时环境太复杂了,
因此一些对运行时环境依赖程度比较复杂的程序至今也很难在Wine或WSL上完美运行,
以至于WSL2抛弃了"运行时环境兼容"的技术路线, 转而采用虚拟机的方式来完美运行Linux系统.
反观Navy的运行时环境却是非常简单, 我们通过不到300行的<code>native.cpp</code>就可以实现它了,
不过如果你理解了其中的概念, 你也就明白类似WSL这些身边的技术是如何工作的了.</p></div></div>
<h3 id="navy中的应用程序">Navy中的应用程序</h3>
<p>有了这些函数库, 我们就可以在Navy中运行更多的程序了.
要运行仙剑奇侠传需要实现较多的功能, 我们先运行一些简单的程序来对你的实现进行测试.</p>
<h4 id="nslider-nju-slider">NSlider (NJU Slider)</h4>
<p>NSlider是Navy中最简单的可展示应用程序, 它是一个支持翻页的幻灯片播放器.
在2018年第二届龙芯杯大赛中, 南京大学赛队通过在自己实现的乱序处理器上运行NSlider,
实现了"在自己构建的全栈计算机系统中播放幻灯片进行决赛现场答辩"的目标.</p>
<p>现在你也可以在自己构建的计算机系统上运行NSlider了, 但你需要先了解SDL的一些概念和API.
SDL的绘图模块引入了一个<code>Surface</code>的概念, 它可以看成一张具有多种属性的画布,
具体可以通过RTFM查阅<code>Surface</code>结构体中的成员含义.
你需要实现以下两个与<code>Surface</code>相关的API:</p>
<ul>
<li><code>SDL_BlitSurface()</code>: 将一张画布中的指定矩形区域复制到另一张画布的指定位置</li>
<li><code>SDL_UpdateRect()</code>: 将画布中的指定矩形区域同步到屏幕上</li>
</ul>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="运行nslider"><i class="fa fa-edit"></i> 运行NSlider</h5></div><div class="panel-body"><p>我们提供了一个脚本来把PDF版本的, 比例为4:3的幻灯片转换成BMP图像, 并拷贝到<code>navy-apps/fsimg/</code>中.
你需要提供一个满足条件的PDF文件, 然后参考相应的README文件进行操作.
但你可能会在转换时遇到一些问题, 具体请自行解决.</p><p>然后在miniSDL中实现<code>SDL_BlitSurface()</code>和<code>SDL_UpdateRect()</code>.
如果你的实现正确, 运行NSlider时将会显示第一张幻灯片.
你很可能是第一次接触SDL的API, 为此你还需要RTFM, 并通过RTFSC来理解已有代码的行为.</p></div></div>
<!-- -->
<div class="panel panel-success"><div class="panel-heading"><h5 class="panel-title" id="注意ramdisk镜像的大小"><i class="fa fa-lightbulb-o"></i> 注意ramdisk镜像的大小</h5></div><div class="panel-body"><p>我们让ramdisk镜像的内容链接到Nanos-lite的数据段, 而又把用户程序加载到内存位置
<code>0x3000000</code>(x86)或<code>0x83000000</code>(mips32或riscv32)附近, 这隐含了一个假设:
ramdisk镜像的大小不能超过48MB. 如果这个假设不满足, ramdisk中的内容就可能被覆盖,
造成难以理解的错误. 因此你需要注意ramdisk镜像的大小, 不要放入过多过大的文件.</p></div></div>
<!-- -->
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="运行nslider2"><i class="fa fa-edit"></i> 运行NSlider(2)</h5></div><div class="panel-body"><p>在miniSDL中实现<code>SDL_WaitEvent()</code>, 它用于等待一个事件.
你需要将NDL中提供的事件封装成SDL事件返回给应用程序,
具体可以通过阅读NSlider的代码来理解SDL事件的格式.
实现正确后, 你就可以在NSlider中进行翻页了, 翻页的操作方式请RTFSC.</p></div></div>
<h4 id="menu-开机菜单">MENU (开机菜单)</h4>
<p>开机菜单是另一个行为比较简单的程序, 它会展示一个菜单, 用户可以选择运行哪一个程序.
为了运行它, 你还需要在miniSDL中实现<code>SDL_FillRect()</code>,
它用于往画布的指定矩形区域中填充指定的颜色.</p>
<p>开机菜单还会显示一些英文字体, 这些字体的信息以BDF格式存储,
Navy中提供了一个libbdf库来解析BDF格式, 生成相应字符的像素信息, 并封装成SDL的<code>Surface</code>.
有了<code>SDL_BlitSurface()</code>之后, 我们就可以很方便地在屏幕上输出字符串的像素信息了.</p>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="运行开机菜单"><i class="fa fa-edit"></i> 运行开机菜单</h5></div><div class="panel-body"><p>正确实现上述API后, 你将会看到一个可以翻页的开机菜单.
但你尝试选择菜单项的时候将会出现错误, 这是因为开机菜单的运行还需要一些系统调用的支持.
我们会在下文进行介绍, 目前通过开机菜单来测试miniSDL即可.</p></div></div>
<h4 id="nterm-nju-terminal">NTerm (NJU Terminal)</h4>
<p>NTerm是一个模拟终端, 它实现了终端的基本功能, 包括字符的键入和回退, 以及命令的获取等.
终端一般会和Shell配合使用, 从终端获取到的命令将会传递给Shell进行处理, Shell又会把信息输出到终端.
NTerm自带一个非常简单的內建Shell(见<code>builtin-sh.cpp</code>), 它默认忽略所有的命令.
NTerm也可以和外部程序进行通信, 但这超出了ICS的范围, 我们在PA中不会使用这个功能.</p>
<p>为了运行NTerm, 你还需要实现miniSDL的两个API:</p>
<ul>
<li><code>SDL_GetTicks()</code>: 它和<code>NDL_GetTicks()</code>的功能类似, 但有一个额外的小要求, 具体请RTFM</li>
<li><code>SDL_PollEvent()</code>: 它和<code>SDL_WaitEvent()</code>不同的是, 如果当前没有任何事件, 就会立即返回</li>
</ul>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="运行nterm"><i class="fa fa-edit"></i> 运行NTerm</h5></div><div class="panel-body"><p>正确实现上述API后, 你会看到NTerm的光标以每秒一次的频率闪烁, 并且可以键入字符.
为了让NTerm可以启动其它程序, 你还需要实现一些系统调用, 我们会在下文进行介绍.</p></div></div>
<!-- -->
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="实现内建的echo命令"><i class="fa fa-edit"></i> 实现内建的echo命令</h5></div><div class="panel-body"><p>在內建Shell中解析命令和你在PA1中实现简易调试器的命令解析非常类似,
而且Navy中的Newlib已经提供了标准库函数了, 有兴趣的同学可以实现一个內建的<code>echo</code>命令.</p></div></div>
<h4 id="flappy-bird">Flappy Bird</h4>
<p>网友开发了一款基于SDL库的Flappy Bird游戏<a href="https://github.com/CecilHarvey/sdlbird" target="_blank">sdlbird</a>, 我们轻松地将它移植到Navy中.
在<code>navy-apps/apps/bird/</code>目录下运行<code>make init</code>, 将会从github上克隆移植后的项目.
这个移植后的项目仍然可以在Linux native上运行: 在<code>navy-apps/apps/bird/repo/</code>目录下运行<code>make run</code>即可
(你可能需要安装一些库, 具体请STFW).
这样的运行方式不会链接Navy中的任何库, 因此你还会听到一些音效, 甚至可以通过点击鼠标来进行游戏.</p>
<p>为了在Navy中运行Flappy Bird, 你还需要实现另一个库SDL_image中的一个API: <code>IMG_Load()</code>.
这个库是基于<a href="https://github.com/nothings/stb" target="_blank">stb项目</a>中的图像解码库来实现的, 用于把解码后的像素封装成SDL的<code>Surface</code>结构,
这样应用程序就可以很容易地在屏幕上显示图片了.
上述API接受一个图片文件的路径, 然后把图片的像素信息封装成SDL的<code>Surface</code>结构并返回.
这个API的一种实现方式如下:</p>
<ol>
<li>用libc中的文件操作打开文件, 并获取文件大小size</li>
<li>申请一段大小为size的内存区间buf</li>
<li>将整个文件读取到buf中</li>
<li>将buf和size作为参数, 调用<code>STBIMG_LoadFromMemory()</code>, 它会返回一个<code>SDL_Surface</code>结构的指针</li>
<li>关闭文件, 释放申请的内存</li>
<li>返回<code>SDL_Surface</code>结构指针</li>
</ol>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="运行flappy-bird"><i class="fa fa-edit"></i> 运行Flappy Bird</h5></div><div class="panel-body"><p>实现<code>IMG_Load()</code>, 在Navy中运行Flappy Bird. 这本质上是一个文件操作的练习.
另外, Flappy Bird默认使用400像素的屏幕高度, 但NEMU的屏幕高度默认为300像素,
为了在NEMU运行Flappy Bird, 你需要将<code>navy-apps/apps/bird/repo/include/Video.h</code>中的
<code>SCREEN_HEIGHT</code>修改为300.</p><p>Flappy Bird默认还会尝试打开声卡播放音效, miniSDL默认会让音频相关的API返回0或<code>NULL</code>,
程序会认为相应操作失败, 但仍然可以在无音效的情况下运行.</p></div></div>
<p>此外, Flappy Bird也是一个适合大家阅读的项目: 阅读它不需要了解过多的知识背景,
而且大家很容易熟悉游戏的规则, 然后就可以去了解游戏的效果是如何用代码实现出来的.</p>
<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>我们在移植游戏的时候, 会按顺序在四种环境中运行游戏:</p><ul>
<li>纯粹的Linux native: 和Project-N的组件没有任何关系, 用于保证游戏本身确实可以正确运行.
在更换库的版本或者修改游戏代码之后, 都会先在Linux native上进行测试.</li>
<li>Navy中的native: 用Navy中的库替代Linux native的库, 测试游戏是否能在Navy库的支撑下正确运行.</li>
<li>AM中的native: 用Nanos-lite, libos和Newlib替代Linux的系统调用和glibc,
测试游戏是否能在Nanos-lite及其运行时环境的支撑下正确运行.</li>
<li>NEMU: 用NEMU替代真机硬件, 测试游戏是否能在NEMU的支撑下正确运行.</li>