-
Notifications
You must be signed in to change notification settings - Fork 154
/
Copy path3.3.html
1402 lines (884 loc) · 123 KB
/
3.3.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.4.html" />
<link rel="prev" href="3.2.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 active" 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>通过<code>yield test</code>了解自陷指令的执行过程后,
我们就可以来理解一个最简单的操作系统如何工作了.</p>
<h3 id="最简单的操作系统">最简单的操作系统</h3>
<p>眼见为实, 我们先来展示一下操作系统究竟可以多简单.
在PA中使用的操作系统叫Nanos-lite, 它是南京大学操作系统Nanos的裁剪版.
是一个为PA量身订造的操作系统. 通过编写Nanos-lite的代码,
你将会认识到操作系统是如何使用硬件提供的机制(也就是ISA和AM),
来支撑程序的运行, 这也符合PA的终极目标.
至于完整版的Nanos, 你将会在下学期的操作系统课程上见识到它的庐山真面目.</p>
<p>我们为大家准备了Nanos-lite的框架代码, 通过执行以下命令获取:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ics2024
bash init.sh nanos-lite
</code></pre>
<p>Nanos-lite包含了后续PA用到的所有模块, 但大部分模块的具体功能都没有实现.
由于硬件(NEMU)的功能是逐渐添加的, Nanos-lite也要配合这个过程,
你会通过<code>nanos-lite/include/common.h</code>中一些与实验进度相关的宏来控制Nanos-lite的功能.
随着实验进度的推进, 我们会逐渐讲解所有的模块, Nanos-lite做的工作也会越来越多.
因此在阅读Nanos-lite的代码时, 你只需要关心和当前进度相关的模块就可以了, 不要纠缠于和当前进度无关的代码.</p>
<pre><code>nanos-lite
├── include
│ ├── common.h
│ ├── debug.h
│ ├── fs.h
│ ├── memory.h
│ └── proc.h
├── Makefile
├── README.md
├── resources
│ └── logo.txt # Project-N logo文本
└── src
├── device.c # 设备抽象
├── fs.c # 文件系统
├── irq.c # 中断异常处理
├── loader.c # 加载器
├── main.c
├── mm.c # 存储管理
├── proc.c # 进程调度
├── ramdisk.c # ramdisk驱动程序
├── resources.S # ramdisk内容和Project-N logo
└── syscall.c # 系统调用处理
</code></pre><p>需要提醒的是, Nanos-lite是运行在AM之上, AM的API在Nanos-lite中都是可用的.
虽然操作系统对我们来说是一个特殊的概念, 但在AM看来,
它只是一个调用AM API的普通C程序而已, 和超级玛丽没什么区别.
同时, 你会再次体会到AM的好处: Nanos-lite的实现可以是架构无关的,
这意味着, 无论你之前选择的是哪一款ISA, 都可以很容易地运行Nanos-lite,
甚至你可以像开发klib那样, 在<code>native</code>上调试你编写的Nanos-lite.</p>
<p>另外, 虽然不会引起明显的误解, 但在引入Nanos-lite之后,
我们还是会在某些地方使用"用户进程"的概念, 而不是"用户程序".
如果你现在不能理解什么是进程, 你只需要把进程作为"正在运行的程序"来理解就可以了.
还感觉不出这两者的区别? 举一个简单的例子吧,
如果你打开了记事本3次, 计算机上就会有3个记事本进程在运行, 但磁盘中的记事本程序只有一个.
进程是操作系统中一个重要的概念, 有关进程的详细知识会在操作系统课上进行介绍.</p>
<p>一开始, 在<code>nanos-lite/include/common.h</code>中所有与实验进度相关的宏都没有定义,
此时Nanos-lite的功能十分简单.
我们来简单梳理一下Nanos-lite目前的行为:</p>
<ol>
<li>打印Project-N的logo, 并通过<code>Log()</code>输出hello信息和编译时间.
需要说明的是, Nanos-lite中定义的<code>Log()</code>宏并不是NEMU中定义的<code>Log()</code>宏.
Nanos-lite和NEMU是两个独立的项目, 它们的代码不会相互影响, 你在阅读代码的时候需要注意这一点.
在Nanos-lite中, <code>Log()</code>宏通过你在<code>klib</code>中编写的<code>printf()</code>输出, 最终会调用TRM的<code>putch()</code>.</li>
<li>调用<code>init_device()</code>对设备进行一些初始化操作. 目前<code>init_device()</code>会直接调用<code>ioe_init()</code>.</li>
<li>初始化ramdisk. 一般来说, 程序应该存放在永久存储的介质中(比如磁盘).
但要在NEMU中对磁盘进行模拟是一个略显复杂工作, 因此先让Nanos-lite把其中的一段内存作为磁盘来使用.
这样的磁盘有一个专门的名字, 叫ramdisk.</li>
<li><code>init_fs()</code>和<code>init_proc()</code>, 分别用于初始化文件系统和创建进程,
目前它们均未进行有意义的操作, 可以忽略它们.</li>
<li>调用<code>panic()</code>结束Nanos-lite的运行.</li>
</ol>
<p>由于Nanos-lite本质上也是一个AM程序, 我们可以采用相同的方式来编译/运行Nanos-lite.
在<code>nanos-lite/</code>目录下执行</p>
<pre><code>make ARCH=$ISA-nemu run
</code></pre><p>即可. 另外如前文所说, 你也可以将Nanos-lite编译到<code>native</code>上并运行, 来帮助你进行调试.</p>
<div class="panel panel-info"><div class="panel-heading"><h5 class="panel-title" id="操作系统是个c程序"><i class="fa fa-comment-o"></i> 操作系统是个C程序</h5></div><div class="panel-body"><p>你也许有点不敢相信, 但框架代码的.c和.h文件无疑蕴含着这铁一般的事实,
甚至连编译方式也看不出什么特殊的地方.
GNU/Linux也是这样: 如果你阅读它的源代码,
就会发现GNU/Linux只不过是个巨大的C程序而已.</p><p>那么和一般的C程序相比, 操作系统究竟有何特殊之处呢?
完成PA3之后, 相信你就会略知一二了.</p></div></div>
<p>框架代码提供的这个操作系统还真的什么都没做!
别着急, 你需要在<code>nanos-lite/include/common.h</code>中定义宏<code>HAS_CTE</code>,
这样以后, Nanos-lite会多进行以下操作:</p>
<ul>
<li>初始化时调用<code>init_irq()</code>函数, 它将通过<code>cte_init()</code>函数初始化CTE</li>
<li>在<code>panic()</code>前调用<code>yield()</code>来触发自陷操作</li>
</ul>
<div class="panel panel-warning"><div class="panel-heading"><h5 class="panel-title" id="为nanos-lite实现正确的事件分发"><i class="fa fa-edit"></i> 为Nanos-lite实现正确的事件分发</h5></div><div class="panel-body"><p>Nanos-lite的事件处理回调函数默认不处理所有事件, 你需要在其中识别出自陷事件<code>EVENT_YIELD</code>,
然后输出一句话即可, 目前无需进行其它操作.</p><p>重新运行Nanos-lite, 如果你的实现正确, 你会看到识别到自陷事件之后输出的信息,
并且最后仍然触发了<code>main()</code>函数末尾设置的<code>panic()</code>.</p></div></div>
<p>确认Nanos-lite可以正确触发自陷操作后, 用户程序就可以将执行流切换到操作系统指定的入口了.
现在我们来解决实现批处理系统的第二个问题: 如何加载用户程序.</p>
<h3 id="加载第一个用户程序">加载第一个用户程序</h3>
<p>在操作系统中, 加载用户程序是由loader(加载器)模块负责的.
我们知道程序中包括代码和数据, 它们都是存储在可执行文件中.
加载的过程就是把可执行文件中的代码和数据放置在正确的内存位置,
然后跳转到程序入口, 程序就开始执行了.
更具体的, 为了实现<code>loader()</code>函数, 我们需要解决以下问题:</p>
<ul>
<li>可执行文件在哪里?</li>
<li>代码和数据在可执行文件的哪个位置?</li>
<li>代码和数据有多少?</li>
<li>"正确的内存位置"在哪里?</li>
</ul>
<p>为了回答第一个问题, 我们还要先说明一下用户程序是从哪里来的.
用户程序运行在操作系统之上, 由于运行时环境的差异,
我们不能把编译到AM上的程序放到操作系统上运行.
为此, 我们准备了一个新的子项目Navy-apps, 专门用于编译出操作系统的用户程序.
通过执行以下命令获取Navy的框架代码:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ics2024
bash init.sh navy-apps
</code></pre>
<p>Navy子项目的结构组织如下, 更多的说明可以阅读<code>README.md</code>:</p>
<pre><code>navy-apps
├── apps # 用户程序
│ ├── am-kernels
│ ├── busybox
│ ├── fceux
│ ├── lua
│ ├── menu
│ ├── nplayer
│ ├── nslider
│ ├── nterm
│ ├── nwm
│ ├── onscripter
│ ├── oslab0
│ └── pal # 仙剑奇侠传
├── fsimg # 根文件系统
├── libs # 运行库
│ ├── libc # Newlib C库
│ ├── libam
│ ├── libbdf
│ ├── libbmp
│ ├── libfixedptc
│ ├── libminiSDL
│ ├── libndl
│ ├── libos # 系统调用的用户层封装
│ ├── libSDL_image
│ ├── libSDL_mixer
│ ├── libSDL_ttf
│ └── libvorbis
├── Makefile
├── README.md
├── scripts
└── tests # 一些测试
</code></pre><p>Navy的<code>Makefile</code>组织和<code>abstract-machine</code>非常类似, 你应该很容易理解它.
其中, <code>navy-apps/libs/libc</code>中是一个名为<a href="https://sourceware.org/newlib/" target="_blank">Newlib</a>的项目,
它是一个专门为嵌入式系统提供的C库, 库中的函数对运行时环境的要求极低.
这对Nanos-lite来说是非常友好的, 我们不必为了配合C库而在Nanos-lite中实现额外的功能.
用户程序的入口位于<code>navy-apps/libs/libos/src/crt0/start.S</code>中的<code>_start()</code>函数,
这里的<code>crt</code>是<code>C RunTime</code>的缩写, <code>0</code>的含义表示最开始.
<code>_start()</code>函数会调用<code>navy-apps/libs/libos/src/crt0/crt0.c</code>中的<code>call_main()</code>函数,
然后调用用户程序的<code>main()</code>函数, 从<code>main()</code>函数返回后会调用<code>exit()</code>结束运行.</p>
<div class="panel panel-danger"><div class="panel-heading"><h5 class="panel-title" id="c库的代码总是对的"><i class="fa fa-bullhorn"></i> C库的代码"总是"对的</h5></div><div class="panel-body"><p>有同学曾经在调bug时认为是C库的代码有bug, 对C库的代码进行修改之后, 果然就可以成功运行了.
事实上, PA的必做内容并不需要修改C库的代码.
修改C库代码使得程序运行成功, 说明有bug的还是你的代码.
修改C库的方式只是在绕开一个你已经发现的bug, 它并没有被解决, 而是回到潜伏的状态,
你很可能会在将来再次遇到它, 解决它所付出的代码也许会更大, 而且遇到的时候你还很难确定它们是不是同一个bug.</p><p>总之, 当你决定要投机取巧的时候, 你需要先冷静分析得失, 再做决定.</p></div></div>
<p>我们要在Nanos-lite上运行的第一个用户程序是<code>navy-apps/tests/dummy/dummy.c</code>.
为了避免和Nanos-lite的内容产生冲突,
我们约定目前用户程序需要被链接到内存位置<code>0x3000000</code>(x86)或<code>0x83000000</code>(mips32或riscv32)附近,
Navy已经设置好了相应的选项(见<code>navy-apps/scripts/$ISA.mk</code>中的<code>LDFLAGS</code>变量).
为了编译dummy, 在<code>navy-apps/tests/dummy/</code>目录下执行</p>
<pre><code>make ISA=$ISA
</code></pre><p>首次在Navy中编译时会从github上获取Newlib等项目并编译, 编译过程中会出现较多warning, 目前可以忽略它们.
编译成功后把<code>navy-apps/tests/dummy/build/dummy-$ISA</code>手动复制并重命名为<code>nanos-lite/build/ramdisk.img</code>,
然后在<code>nanos-lite/</code>目录下执行</p>
<pre><code>make ARCH=$ISA-nemu
</code></pre><p>会生成Nanos-lite的可执行文件, 编译期间会把ramdisk镜像文件<code>nanos-lite/build/ramdisk.img</code>
包含进Nanos-lite成为其中的一部分(在<code>nanos-lite/src/resources.S</code>中实现).
现在的ramdisk十分简单, 它只有一个文件, 就是我们将要加载的用户程序<code>dummy</code>,
这其实已经回答了上述第一个问题: 可执行文件位于ramdisk偏移为0处, 访问它就可以得到用户程序的第一个字节.</p>
<p>为了回答剩下的问题, 我们首先需要了解可执行文件是如何组织的.
你应该已经在课堂上学习过ELF文件格式了, 它除了包含程序本身的代码和静态数据之外,
还包括一些用来描述它们的信息, 否则我们连代码和数据之间的分界线在哪里都不知道.
这些信息描述了可执行文件的组织形式, 不同组织形式形成了不同格式的可执行文件,
例如Windows主流的可执行文件是<a href="http://en.wikipedia.org/wiki/Portable_Executable" target="_blank">PE(Portable Executable)</a>格式,
而GNU/Linux主要使用<a href="http://en.wikipedia.org/wiki/Executable_and_Linkable_Format" target="_blank">ELF(Executable and Linkable Format)</a>格式.
因此一般情况下, 你不能在Windows下把一个可执行文件拷贝到GNU/Linux下执行, 反之亦然.
ELF是GNU/Linux可执行文件的标准格式, 这是因为GNU/Linux遵循System V ABI(<a href="http://stackoverflow.com/questions/2171177/what-is-application-binary-interface-abi" target="_blank">Application Binary Interface</a>). </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>我们提到了代码和数据都在可执行文件里面, 但却没有提到堆(heap)和栈(stack).
为什么堆和栈的内容没有放入可执行文件里面?
那程序运行时刻用到的堆和栈又是怎么来的?
AM的代码是否能给你带来一些启发?</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>如果你在GNU/Linux下执行一个从Windows拷过来的可执行文件, 将会报告"格式错误".
思考一下, GNU/Linux是如何知道"格式错误"的?</p></div></div>
<p>ELF文件提供了两个视角来组织一个可执行文件, 一个是面向链接过程的section视角,
这个视角提供了用于链接与重定位的信息(例如符号表);
另一个是面向执行的segment视角, 这个视角提供了用于加载可执行文件的信息.
通过<code>readelf</code>命令, 我们还可以看到section和segment之间的映射关系:
一个segment可能由0个或多个section组成, 但一个section可能不被包含于任何segment中.</p>
<p>我们现在关心的是如何加载程序, 因此我们重点关注segment的视角.
ELF中采用program header table来管理segment, program header table的一个表项描述了一个segment的所有属性,
包括类型, 虚拟地址, 标志, 对齐方式, 以及文件内偏移量和segment大小.
根据这些信息, 我们就可以知道需要加载可执行文件的哪些字节了,
同时我们也可以看到, 加载一个可执行文件并不是加载它所包含的所有内容,
只要加载那些与运行时刻相关的内容就可以了, 例如调试信息和符号表就不必加载.
我们可以通过判断segment的<code>Type</code>属性是否为<code>PT_LOAD</code>来判断一个segment是否需要加载.</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>使用<code>readelf</code>查看一个ELF文件的信息, 你会看到一个segment包含两个大小的属性,
分别是<code>FileSiz</code>和<code>MemSiz</code>, 这是为什么?
再仔细观察一下, 你会发现<code>FileSiz</code>通常不会大于相应的<code>MemSiz</code>, 这又是为什么?</p></div></div>
<p>我们通过下面的图来说明如何根据segment的属性来加载它:</p>
<pre><code> +-------+---------------+-----------------------+
| |...............| |
| |...............| | ELF file
| |...............| |
+-------+---------------+-----------------------+
0 ^ |
|<------+------>|
| | |
| |
| +----------------------------+
| |
Type | Offset VirtAddr PhysAddr |FileSiz MemSiz Flg Align
LOAD +-- 0x001000 0x03000000 0x03000000 +0x1d600 0x27240 RWE 0x1000
| | |
| +-------------------+ |
| | |
| | | | |
| | | | |
| | +-----------+ --- |
| | |00000000000| ^ |
| | --- |00000000000| | |
| | ^ |...........| | |
| | | |...........| +------+
| +--+ |...........| |
| | |...........| |
| v |...........| v
+-------> +-----------+ ---
| |
| |
Memory
</code></pre><p>你需要找出每一个需要加载的segment的<code>Offset</code>, <code>VirtAddr</code>, <code>FileSiz</code>和<code>MemSiz</code>这些参数.
其中相对文件偏移<code>Offset</code>指出相应segment的内容从ELF文件的第<code>Offset</code>字节开始, 在文件中的大小为<code>FileSiz</code>,
它需要被分配到以<code>VirtAddr</code>为首地址的虚拟内存位置, 在内存中它占用大小为<code>MemSiz</code>.
也就是说, 这个segment使用的内存就是<code>[VirtAddr, VirtAddr + MemSiz)</code>这一连续区间,
然后将segment的内容从ELF文件中读入到这一内存区间,
并将<code>[VirtAddr + FileSiz, VirtAddr + MemSiz)</code>对应的物理区间清零.</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>为什么需要将 <code>[VirtAddr + FileSiz, VirtAddr + MemSiz)</code> 对应的物理区间清零?</p></div></div>
<p>关于程序从何而来, 可以参考一篇文章: <a href="http://www.tenouk.com/ModuleW.html" target="_blank">COMPILER, ASSEMBLER, LINKER AND LOADER: A BRIEF STORY</a>.
如果你希望查阅更多与ELF文件相关的信息, 请参考</p>
<pre><code>man 5 elf
</code></pre><p>由于ELF文件在ramdisk中, 框架代码提供了一些ramdisk相关的函数(在<code>nanos-lite/src/ramdisk.c</code>中定义),
你可以使用它们来实现loader的功能:</p>
<pre><code class="lang-c"><span class="hljs-comment">// 从ramdisk中`offset`偏移处的`len`字节读入到`buf`中</span>
<span class="hljs-keyword">size_t</span> ramdisk_read(<span class="hljs-keyword">void</span> *buf, <span class="hljs-keyword">size_t</span> offset, <span class="hljs-keyword">size_t</span> len);