-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.xml
1741 lines (1741 loc) · 361 KB
/
index.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
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
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>断念梦的站点 – 首页</title><link>https://desistdaydream.github.io/</link><description>Recent content in 首页 on 断念梦的站点</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><atom:link href="https://desistdaydream.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>Blog: 公众号,码农的荒岛求生-操作系统话题系列文章</title><link>https://desistdaydream.github.io/blog/copy/%E5%85%AC%E4%BC%97%E5%8F%B7%E7%A0%81%E5%86%9C%E7%9A%84%E8%8D%92%E5%B2%9B%E6%B1%82%E7%94%9F-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E8%AF%9D%E9%A2%98%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0/%E5%85%AC%E4%BC%97%E5%8F%B7%E7%A0%81%E5%86%9C%E7%9A%84%E8%8D%92%E5%B2%9B%E6%B1%82%E7%94%9F-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E8%AF%9D%E9%A2%98%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://desistdaydream.github.io/blog/copy/%E5%85%AC%E4%BC%97%E5%8F%B7%E7%A0%81%E5%86%9C%E7%9A%84%E8%8D%92%E5%B2%9B%E6%B1%82%E7%94%9F-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E8%AF%9D%E9%A2%98%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0/%E5%85%AC%E4%BC%97%E5%8F%B7%E7%A0%81%E5%86%9C%E7%9A%84%E8%8D%92%E5%B2%9B%E6%B1%82%E7%94%9F-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E8%AF%9D%E9%A2%98%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0/</guid><description>
<p><a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg4OTYzODM4Mw==&amp;action=getalbum&amp;album_id=1923404049802985480&amp;scene=173&amp;from_msgid=2247485655&amp;from_itemidx=1&amp;count=3&amp;nolastread=1#wechat_redirect">系列文章目录</a></p></description></item><item><title>Blog: ChatGPT 团队是如何使用Kubernetes的</title><link>https://desistdaydream.github.io/blog/copy/%E4%BA%91%E5%8E%9F%E7%94%9F/ChatGPT-%E5%9B%A2%E9%98%9F%E6%98%AF%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Kubernetes%E7%9A%84/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://desistdaydream.github.io/blog/copy/%E4%BA%91%E5%8E%9F%E7%94%9F/ChatGPT-%E5%9B%A2%E9%98%9F%E6%98%AF%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8Kubernetes%E7%9A%84/</guid><description>
<p><a href="https://mp.weixin.qq.com/s/u7zibC7UmMSAYotWZ81eYg">原文链接</a></p>
<p><a href="https://openai.com/research/scaling-kubernetes-to-7500-nodes">https://openai.com/research/scaling-kubernetes-to-7500-nodes</a></p>
<p>在本文中,OpenAI 的工程师团队分享了他们在 Kubernetes 集群扩展过程中遇到的各种挑战和解决方案,以及他们取得的性能和效果。</p>
<p>我们已经将 Kubernetes 集群扩展到 7500 个节点,为大型模型(如 GPT-3、 CLIP 和 DALL·E)创建了可扩展的基础设施,同时也为快速小规模迭代研究(如 神经语言模型的缩放定律)创建了可扩展的基础设施。</p>
<p>将单个 Kubernetes 集群扩展到这种规模很少见,但好处是能够提供一个简单的基础架构,使我们的机器学习研究团队能够更快地推进并扩展,而无需更改代码。</p>
<p>自上次发布关于扩展到 2500 个节点的帖子以来,我们继续扩大基础设施以满足研究人员的需求,在此过程中学到了许多的经验教训。本文总结了这些经验教训,以便 Kubernetes 社区里的其他人也能从中受益,并最后会介绍下我们仍然面临的问题,我们也将继续解决这些问题。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/YriaiaJPb26VOiciaV2ibeVb4gXQtYocv76XKdUh15yLPEl9eIzUgtNqcpB4tp85WFRKYQGYlv5dlPjib6OTbqCqeUdg/640?wx_fmt=png&amp;wxfrom=13&amp;tp=wxpic" alt=""></p>
<p>我们的工作负载</p>
<p>在深入探讨之前,我们着重描述一下我们的工作负载。我们在 Kubernetes 上运行的应用程序和硬件与大家在普通公司遇到的可能相当不同。因此,我们的问题及解决方案可能与你自己的设置匹配,也可能不匹配!</p>
<p>一个大型的机器学习作业跨越许多节点,当它可以访问每个节点上的所有硬件资源时,运行效率最高。这允许 GPU 直接使用 NVLink 进行交叉通信,或者 GPU 使用 GPUDirect 直接与 NIC 进行通信。因此,对于我们的许多工作负载,单个 Pod 占用整个节点。任何 NUMA、CPU 或 PCIE 资源争用都不是调度的因素。装箱或碎片化不是常见的问题。我们目前的集群具有完全的二分带宽,因此我们也不考虑机架或网络拓扑。所有这些都意味着,虽然我们有许多节点,但调度程序的负载相对较低。</p>
<p>话虽如此,kube-scheduler 的负载是有波动的。一个新的作业可能由许多数百个 Pod 同时创建组成,然后返回到相对较低的流失率。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/YriaiaJPb26VOiciaV2ibeVb4gXQtYocv76XKLpfylgOVT7BzEulB0dicicTPY64JIp4CzqozxGqiaibbxiawSQliaFeicVhWA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1&amp;tp=wxpic" alt=""></p>
<p>我们最大的作业运行 MPI,作业中的所有 Pod 都参与一个单一的 MPI 通信器。如果任何一个参与的 Pod 挂掉,整个作业就会停止,需要重新启动。作业会定期进行检查点,当重新启动时,它会从上一个检查点恢复。因此,我们认为 Pod 是半有状态的——被删掉的 Pod 可以被替换并且工作可以继续,但这样做会造成干扰,应该尽量减少发生。</p>
<p>我们并不太依赖 Kubernetes 的负载均衡。我们的 HTTPS 流量非常少,不需要进行 A/B 测试、蓝 / 绿或金丝雀部署。Pod 使用 SSH 直接通过 Pod IP 地址与 MPI 进行通信,而不是通过服务端点。服务“发现”是有限的;我们只在作业启动时进行一次查找,查找哪些 Pod 参与 MPI。</p>
<p>大多数作业与某种形式的 Blob 存储进行交互。它们通常会直接从 Blob 存储流式传输一些数据集的分片或检查点,或将其缓存到快速的本地临时磁盘中。我们有一些 PersistentVolumes,用于那些需要 POSIX 语义的情况,但 Blob 存储更具可扩展性,而且不需要缓慢的分离 / 附加操作。</p>
<p>最后,我们的工作性质本质上是研究,这意味着工作负载本身是不断变化的。虽然超级计算团队努力提供我们认为达到“生产”质量水平的计算基础架构,但在该集群上运行的应用程序寿命很短,它们的开发人员会快速迭代。因此,随时可能出现新的使用模式,这些模式会挑战我们对趋势和适当权衡的设定。我们需要一个可持续的系统,同时也能让我们在事情发生变化时快速做出响应。</p>
<p>网 络</p>
<p>随着集群内节点和 Pod 数量的增加,我们发现 Flannel 难以满足所需的吞吐量。因此,我们转而使用 Azure VMSS 的本地 Pod 网络技术和相关 CNI 插件来配置 IP。这使我们的 Pod 能够获得主机级别的网络吞吐量。</p>
<p>我们转而使用别名 IP 地址的另一个原因是,在我们最大的集群中,可能会同时使用约 20 万个 IP 地址。在测试了基于路由的 Pod 网络后,我们发现能够使用的路由数明显存在限制。</p>
<p>避免封装会增加底层 SDN 或路由引擎的需求,虽然这使我们的网络设置变得简单。添加 VPN 或隧道可以在不需要任何其他适配器的情况下完成。我们不需要担心由于某部分网络具有较低的 MTU 而导致的分组分段。网络策略和流量监控很简单;没有关于数据包源和目的地的歧义。</p>
<p>我们使用主机上的 iptables 标记来跟踪每个 Namespace 和 Pod 的网络资源使用情况,这使研究人员可以可视化他们的网络使用模式。特别是,由于我们的许多实验具有不同的 Internet 和 Pod 内通信模式,因此能够调查任何瓶颈发生的位置通常是非常有意义的。</p>
<p>可以使用 iptables <code>mangle</code> 规则任意标记符合特定条件的数据包。以下是我们用来检测流量是内部流量还是 Internet 流量的规则。<code>FORWARD</code> 规则涵盖了来自 Pod 的流量,而 <code>INPUT</code> 和 <code>OUTPUT</code> 规则涵盖了主机上的流量:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#204a87;font-weight:bold">iptables</span> <span style="color:#4e9a06">-t</span> <span style="color:#4e9a06">mangle</span> <span style="color:#4e9a06">-A</span> <span style="color:#4e9a06">INPUT</span> <span style="color:#4e9a06">!</span> <span style="color:#4e9a06">-s</span> <span style="color:#0000cf;font-weight:bold">10</span><span style="color:#4e9a06">.0.0.0/8</span> <span style="color:#4e9a06">-m</span> <span style="color:#4e9a06">comment</span> <span style="color:#4e9a06">--comment</span> <span style="color:#4e9a06">&#34;iptables-exporter</span> <span style="color:#4e9a06">openai</span> <span style="color:#4e9a06">traffic=internet-in&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#4e9a06">iptables</span> <span style="color:#4e9a06">-t</span> <span style="color:#4e9a06">mangle</span> <span style="color:#4e9a06">-A</span> <span style="color:#4e9a06">FORWARD</span> <span style="color:#4e9a06">!</span> <span style="color:#4e9a06">-s</span> <span style="color:#0000cf;font-weight:bold">10</span><span style="color:#4e9a06">.0.0.0/8</span> <span style="color:#4e9a06">-m</span> <span style="color:#4e9a06">comment</span> <span style="color:#4e9a06">--comment</span> <span style="color:#4e9a06">&#34;iptables-exporter</span> <span style="color:#4e9a06">openai</span> <span style="color:#4e9a06">traffic=internet-in&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#4e9a06">iptables</span> <span style="color:#4e9a06">-t</span> <span style="color:#4e9a06">mangle</span> <span style="color:#4e9a06">-A</span> <span style="color:#4e9a06">OUTPUT</span> <span style="color:#4e9a06">!</span> <span style="color:#4e9a06">-d</span> <span style="color:#0000cf;font-weight:bold">10</span><span style="color:#4e9a06">.0.0.0/8</span> <span style="color:#4e9a06">-m</span> <span style="color:#4e9a06">comment</span> <span style="color:#4e9a06">--comment</span> <span style="color:#4e9a06">&#34;iptables-exporter</span> <span style="color:#4e9a06">openai</span> <span style="color:#4e9a06">traffic=internet-out&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#4e9a06">iptables</span> <span style="color:#4e9a06">-t</span> <span style="color:#4e9a06">mangle</span> <span style="color:#4e9a06">-A</span> <span style="color:#4e9a06">FORWARD</span> <span style="color:#4e9a06">!</span> <span style="color:#4e9a06">-d</span> <span style="color:#0000cf;font-weight:bold">10</span><span style="color:#4e9a06">.0.0.0/8</span> <span style="color:#4e9a06">-m</span> <span style="color:#4e9a06">comment</span> <span style="color:#4e9a06">--comment</span> <span style="color:#4e9a06">&#34;iptables-exporter</span> <span style="color:#4e9a06">openai</span> <span style="color:#4e9a06">traffic=internet-out&#34;</span>
</span></span></code></pre></div><p>一旦标记,iptables 将开始计数以跟踪匹配该规则的字节数和数据包数。你可以使用 <code>iptables</code> 本身来查看这些计数器:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>% iptables -t mangle -L -v
</span></span><span style="display:flex;"><span>Chain FORWARD <span style="color:#ce5c00;font-weight:bold">(</span>policy ACCEPT 50M packets, 334G bytes<span style="color:#ce5c00;font-weight:bold">)</span>
</span></span><span style="display:flex;"><span> pkts bytes target prot opt in out <span style="color:#204a87">source</span> destination
</span></span><span style="display:flex;"><span>....
</span></span><span style="display:flex;"><span>1253K 555M all -- any any anywhere !10.0.0.0/8 /* iptables-exporter openai <span style="color:#000">traffic</span><span style="color:#ce5c00;font-weight:bold">=</span>internet-out */
</span></span><span style="display:flex;"><span>1161K 7937M all -- any any !10.0.0.0/8 anywhere /* iptables-exporter openai <span style="color:#000">traffic</span><span style="color:#ce5c00;font-weight:bold">=</span>internet-in */
</span></span></code></pre></div><p>我们使用名为 iptables-exporter 的开源 Prometheus 导出器将这些数据追踪到我们的监控系统中。这是一种简单的方法,可以跟踪与各种不同类型的条件匹配的数据包。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/YriaiaJPb26VOiciaV2ibeVb4gXQtYocv76XKd8NicaEsHsoGOvcBJMt435PARbypib0ENCBwpeXTibnrp9mkYENFG0Hfg/640?wx_fmt=png" alt=""></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/YriaiaJPb26VOiciaV2ibeVb4gXQtYocv76XKQOOXeibMYDia4Eoic4E7iaNI2uPCa1HOVeY75vj4D2SfSnGVm5wVmDx9Ug/640?wx_fmt=png" alt=""></p>
<p>我们网络模型中比较独特的一点是,我们完全向研究人员公开节点、Pod 和 Service 网络 CIDR 范围。我们采用集线器和分支的网络模型,并使用本机节点和 Pod CIDR 范围路由该流量。研究人员连接到中心枢纽,然后可以访问任何一个单独的集群(分支)。但是这些集群本身无法相互通信。这确保了集群保持隔离、没有跨集群依赖,可以防止故障隔离中的故障传播。</p>
<p>我们使用一个“NAT”主机来翻译从集群外部传入的服务网络 CIDR 范围的流量。这种设置为我们的研究人员提供了很大的灵活性,他们可以选择各种不同类型的网络配置进行实验。</p>
<p>API 服务器</p>
<p>Kubernetes 的 API Server 和 etcd 是保持集群健康运行的关键组件,因此我们特别关注这些系统的压力。我们使用 kube-prometheus 提供的 Grafana 仪表板以及额外的内部仪表板。我们发现,将 HTTP 状态码 429(请求太多)和 5xx(服务器错误)的速率作为高级信号警报是有用的。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/YriaiaJPb26VOiciaV2ibeVb4gXQtYocv76XKDtNcvSjnOqmZ6iccOSmwmricspB55500xvHpBMAI6HiaGfcCtSjqq3ljA/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1&amp;tp=wxpic" alt=""></p>
<p>虽然有些人在 kube 内部运行 API 服务器,但我们一直在集群外运行它们。etcd 和 API 服务器都在它们自己的专用节点上运行。我们的最大集群运行 5 个 API 服务器和 5 个 etcd 节点,以分散负载并尽可能减少发生故障后带来的影响。自从我们在 上一篇博文 中提到的将 Kubernetes 事件拆分到它们自己的 etcd 集群中以来,我们没有遇到 etcd 的任何值得注意的问题。API 服务器是无状态的,通常很容易在自我修复的实例组或扩展集中运行。我们尚未尝试构建任何自我修复 etcd 集群的自动化,因为发生事故非常罕见。</p>
<p>API 服务器可能会占用相当多的内存,并且往往会与集群中的节点数量成线性比例。对于我们有 7500 个节点的集群,我们观察到每个 API 服务器使用高达 70GB 的堆内存,因此幸运地是,未来这应该仍然在硬件能力范围之内。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/YriaiaJPb26VOiciaV2ibeVb4gXQtYocv76XKh2rebxQzCsojXTUN9nDTDZbYWPMpq9ORPlVQfzHwh4sbM4VCztKibWQ/640?wx_fmt=png&amp;tp=wxpic&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1" alt=""></p>
<p>API Servers 受到压力的主要来源之一就是对 Endpoints 的 WATCH。在整个集群中有一些服务,如“kubelet”和“node-exporter”,其中每个节点都是成员。当一个节点被添加或从集群中删除时,这个 WATCH 将被触发。通常,由于每个节点本身都通过 kube-proxy 监视 <code>kubelet</code> 服务,因此这些响应中所需的数量和带宽将是 N2 非常大,有时会达到 1GB/s 或更高。Kubernetes 1.17 中推出的 EndpointSlices 大大降低了这种负载,减少达 1000 倍。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/YriaiaJPb26VOiciaV2ibeVb4gXQtYocv76XKwwo1NJytItYTcNF6oeOvs5TeLaBNqZuJa7LKFysickwuoBcHEiacZltQ/640?wx_fmt=png&amp;wxfrom=5&amp;wx_lazy=1&amp;wx_co=1&amp;tp=wxpic" alt=""></p>
<p>总的来说,我们会非常注意随着集群规模增大而增加的 API Server 请求。我们尽量避免任何 DaemonSets 与 API Server 交互。在需要每个节点监视更改的情况下,引入缓存服务(例如 Datadog Cluster Agent)作为中介,似乎是避免集群范围瓶颈的良好模式。</p>
<p>随着集群的增长,我们对集群的实际自动伸缩越来越少。但当一次性自动扩展太多时,我们偶尔会遇到问题。当新节点加入集群时会生成大量请求,一次性添加数百个节点可能会超过 API 服务器容量的负荷。稍微平滑一下这个过程,即使只有几秒钟也有助于避免宕机。</p>
<p>时间序列度量与 Prometheus 和 Grafana</p>
<p>我们使用 Prometheus 收集时间序列度量数据,并使用 Grafana 进行图形、仪表板和警报。我们从 kube-prometheus 部署开始收集了各种各样的度量数据,并使用了一些良好的仪表板进行可视化。随着节点数量的不断增加,我们开始遇到 Prometheus 收集的度量数据数量过多的问题。尽管 kube-prometheus 公开了许多有用的数据,但我们实际上并没有查看所有的度量数据,一些数据也过于细化,无法有效地进行收集、存储和查询。因此,我们使用 Prometheus 规则从被摄入的度量数据中“删掉”一些数据。</p>
<p>有一段时间,我们遇到了 Prometheus 消耗越来越多的内存问题,最终导致容器崩溃并出现 Out-Of-Memory 错误(OOM)。即使为应用程序分配了大量的内存容量,这种情况似乎仍然会发生。更糟糕的是,它在崩溃时会花费很多时间在启动时回放预写日志文件,直到它再次可用。最终,我们发现了这些 OOM 的来源是 Grafana 和 Prometheus 之间的交互,其中 Grafana 使用 <code>/api/v1/series</code> API 查询 <code>{le!=&quot;&quot;}</code>(基本上是“给我所有的直方图度量”)。<code>/api/v1/series</code> 的实现在时间和空间上没有限制,对于具有大量结果的查询,这将不断消耗更多的内存和时间。即使请求者已经放弃并关闭了连接,它也会继续增长。对于我们来说,内存永远不够,而 Prometheus 最终会崩溃。因此,我们修补了 Prometheus,将此 API 包含在上下文中以强制执行超时,从而完全解决了问题。</p>
<p>虽然 Prometheus 崩溃的次数大大减少,但在我们需要重新启动它的时候,WAL 回放仍然是一个问题。通常需要多个小时来回放所有 WAL 日志,直到 Prometheus 开始收集新的度量数据并提供服务。在 Robust Perception 的帮助下,我们发现将 <code>GOMAXPROCS=24</code> 应用于服务器可以显著提高性能。在 WAL 回放期间,Prometheus 尝试使用所有核心,并且对于具有大量核心的服务器,争用会降低所有性能。</p>
<p>我们正在探索新的选项来增加我们的监控能力,下面“未解决的问题”部分将对此进行描述。</p>
<p>健康检查</p>
<p>对于如此庞大的集群,我们当然依赖自动化来检测并从集群中移除行为不当的节点。随着时间的推移,我们建立了许多健康检查系统。</p>
<p>被动健康检查</p>
<p>某些健康检查是被动的,总是在所有节点上运行。这些检查监视基本的系统资源,例如网络可达性、坏盘或满盘,或者 GPU 错误。GPU 以许多不同的方式出现问题,但一个容易出现的常见问题是“不可纠正的 ECC 错误”。Nvidia 的数据中心 GPU 管理器(DCGM)工具使查询这个问题和许多其他“Xid”错误变得容易。我们跟踪这些错误的一种方式是通过 dcgm-exporter 将指标收集到我们的监控系统 Prometheus 中。这将出现为 <code>DCGM_FI_DEV_XID_ERRORS</code> 指标,并设置为最近发生的错误代码。此外,NVML 设备查询 API 公开了有关 GPU 的健康和操作的更详细信息。</p>
<p>一旦检测到错误,它们通常可以通过重置 GPU 或系统来修复,但在某些情况下确实需要更换基础 GPU。</p>
<p>另一种健康检查是跟踪来自上游云提供商的维护事件。每个主要的云提供商都公开了一种方式来了解当前 VM 是否需要进行会最终导致中断的、即将发生的维护事件。VM 可能需要重新启动以应用底层的超级管理程序补丁,或者将物理节点替换为其他硬件。</p>
<p>这些被动健康检查在所有节点上不断运行。如果健康检查开始失败,节点将自动划分,因此不会在节点上安排新的 Pod。对于更严重的健康检查失败,我们还将尝试 Pod 驱逐,以要求当前运行的所有 Pod 立即退出。这仍然取决于 Pod 本身,可通过 Pod 故障预算进行配置来决定是否允许此驱逐发生。最终,无论是在所有 Pod 终止之后,还是在 7 天过去之后(我们的服务级别协议的一部分),我们都将强制终止 VM。</p>
<p>活动 GPU 测试</p>
<p>不幸的是,并非所有 GPU 问题都会通过 DCGM 可见的错误代码表现出来。我们建立了自己的测试库,通过对 GPU 进行测试来捕捉其他问题,并确保硬件和驱动程序的行为符合预期。这些测试无法在后台运行 - 它们需要独占 GPU 运行数秒钟或数分钟。</p>
<p>我们首先在节点启动时运行这些测试,使用我们称之为“预检(preflight)”的系统。所有节点都会附带一个“预检”污点和标签加入集群。这个污点会阻止普通 Pod 被调度到节点上。我们配置了一个 DaemonSet,在所有带有此标签的节点上运行预检测试 Pod。测试成功完成后,测试本身将删除污点和标签,然后该节点就可供一般使用。</p>
<p>我们还定期在节点的生命周期中运行这些测试。我们将其作为 CronJob 运行,允许它着陆在集群中的任何可用节点上。哪些节点会被测试到可能有些随机和不受控制,但我们发现随着时间的推移,它提供了足够的覆盖率,并且最小化了协调或干扰。</p>
<p>配额和资源使用</p>
<p>随着集群规模的扩大,研究人员开始发现他们难以获取分配给他们的全部容量。传统的作业调度系统有许多不同的功能,可以公平地在竞争团队之间运行工作,而 Kubernetes 没有这些功能。随着时间的推移,我们从这些作业调度系统中汲取灵感,并以 Kubernetes 原生的方式构建了几个功能。</p>
<p>团队污点</p>
<p>我们在每个集群中都有一个服务,称为“team-resource-manager”,具有多个功能。它的数据源是一个 ConfigMap,为在给定集群中具有容量的所有研究团队指定了 (节点选择器、应用的团队标签、分配数量) 元组。它会将当前节点与这些元组进行对比,并使用 <code>openai.com/team=teamname:NoSchedule</code> 的污点对适当数量的节点进行标记。</p>
<p>“team-resource-manager”还有一个入站的 webhook 服务,因此在提交每个作业时会根据提交者的团队成员身份应用相应的容忍度。使用污点使我们能够灵活地限制 Kubernetes Pod 调度程序,例如允许较低优先级的 Pod 具有 &ldquo;any&rdquo; 容忍度,这样团队可以借用彼此的容量,而无需进行大量协调。</p>
<p>CPU 和 GPU balloons</p>
<p>除了使用集群自动缩放器动态扩展我们基于虚拟机的集群之外,我们还使用它来纠正(删除和重新添加)集群中的不健康成员。我们通过将集群的 &ldquo;最小值&rdquo; 设置为零、&ldquo;最大值&rdquo; 设置为可用容量来实现这一点。然而,如果 cluster-autoscaler 发现有空闲节点,它将尝试缩小到只需要的容量。由于多种原因(VM 启动延迟、预分配成本、上面提到的 API 服务器影响),这种空闲缩放并不理想。</p>
<p>因此,我们为 CPU 和 GPU 主机都引入了“球形”部署。这个部署包含一个具有 &ldquo;最大值&rdquo; 数量的低优先级 Pod 副本集。这些 Pod 占用节点内的资源,因此自动缩放器不会将它们视为空闲。但由于它们是低优先级的,调度程序可以立即将它们驱逐出去,以腾出空间进行实际工作。(我们选择使用 Deployment 而不是 DaemonSet,以避免将 DaemonSet 视为节点上的空闲工作负载。)</p>
<p>需要注意的是,我们使用 pod 反亲和性(anti-affinity)来确保 pod 在节点之间均匀分布。Kubernetes 调度器的早期版本存在一个 O(N^2) 的性能问题,与 pod 反亲和性有关。自 Kubernetes 1.18 版本以后,这个问题已经得到了纠正。</p>
<p>Gang 调度</p>
<p>我们的实验通常涉及一个或多个 StatefulSets,每个 StatefulSet 操作不同部分的训练任务。对于优化器,研究人员需要在进行任何训练之前调度 StatefulSet 的所有成员(因为我们通常使用 MPI 在优化器成员之间协调,而 MPI 对组成员变化很敏感)。</p>
<p>然而再默认情况下,Kubernetes 不一定会优先满足某个 StatefulSet 的所有请求。例如,如果两个实验都请求 100%的集群容量,那么 Kubernetes 可能只会调度给每个实验需要的一半 Pod,这会导致死锁,使两个实验都无法进行。</p>
<p>我们尝试了一些需要自定义调度程序的方法,但遇到了一些与正常 Pod 调度方式冲突的边缘情况。Kubernetes 1.18 引入了核心 Kubernetes 调度程序的插件体系结构,使本地添加此类功能变得更加容易。我们最近选择了 Coscheduling 插件作为解决此问题的方法。</p>
<p>未解决的问题</p>
<p>随着 Kubernetes 集群规模的扩大,我们仍有许多问题需要解决。其中一些问题包括:</p>
<p>指标</p>
<p>在如今的规模下,Prometheus 内置的 TSDB 存储引擎很难压缩,并且每次重新启动时需要长时间回放 WAL(预写式日志)。查询还往往会导致“查询处理会加载过多样本”的错误。我们正在迁移到不同的、与 Prometheus 兼容的存储和查询引擎。大家可以期待下我们未来的博客文章,看看它的表现如何!</p>
<p>Pod 网络流量整形</p>
<p>随着集群规模的扩大,每个 Pod 的互联网带宽量被计算了出来。每个人的聚合互联网带宽需求变得非常大,我们的研究人员现在有能力会意外地对互联网上的其他位置施加重大资源压力,例如要下载的数据集和要安装的软件包。</p>
<p>结 论</p>
<p>Kubernetes 是一个非常灵活的平台,可以满足我们的研究需求。它具有满足我们所面临的最苛刻工作负载的能力。尽管它仍有许多需要改进的地方,但 OpenAI 的超级计算团队将继续探索 Kubernetes 的可扩展性。</p></description></item><item><title>Blog: 反思</title><link>https://desistdaydream.github.io/blog/%E5%8F%8D%E6%80%9D/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://desistdaydream.github.io/blog/%E5%8F%8D%E6%80%9D/</guid><description>
<h1 id="概述">概述<a class="td-heading-self-link" href="#%e6%a6%82%e8%bf%b0" aria-label="Heading self-link"></a></h1>
<blockquote>
<p>参考:</p>
<ul>
<li></li>
</ul>
</blockquote>
<p><a href="https://mp.weixin.qq.com/s/VQUEXNJCgj-ydH44QTpeBA">https://mp.weixin.qq.com/s/VQUEXNJCgj-ydH44QTpeBA</a></p>
<p>伟大的开发者,你们过得开心吗?</p>
<p>互联网做得太出色了,以至于很多人把它看作某种像太平洋一样的自然资源,而非人造的。—— Alan Kay.</p>
<p><img src="https://mmbiz.qpic.cn/sz_mmbiz_jpg/dkwuWwLoRK8nrric2k6n7z179ev9Na9MamauNIbjribicfzQVter39E6ZqgmFuiaKgZhKfDlsCfiaV5M9Ws7PJIoOsw/640?wxfrom=12&amp;tp=wxpic&amp;usePicPrefetch=1&amp;wx_fmt=jpeg" alt="800"></p></description></item><item><title>Blog: 灵魂拷问,上 Kubernetes 有什么业务价值?</title><link>https://desistdaydream.github.io/blog/copy/%E4%BA%91%E5%8E%9F%E7%94%9F/%E7%81%B5%E9%AD%82%E6%8B%B7%E9%97%AE%E4%B8%8A-Kubernetes-%E6%9C%89%E4%BB%80%E4%B9%88%E4%B8%9A%E5%8A%A1%E4%BB%B7%E5%80%BC/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://desistdaydream.github.io/blog/copy/%E4%BA%91%E5%8E%9F%E7%94%9F/%E7%81%B5%E9%AD%82%E6%8B%B7%E9%97%AE%E4%B8%8A-Kubernetes-%E6%9C%89%E4%BB%80%E4%B9%88%E4%B8%9A%E5%8A%A1%E4%BB%B7%E5%80%BC/</guid><description>
<p><a href="https://mp.weixin.qq.com/s/a3NE5fSpZIM9qlOofGTMWQ">原文链接</a></p>
<p>本文整理自 2020 年 7 月 22 日《基于 Kubernetes 与 OAM 构建统一、标准化的应用管理平台》主题线上网络研讨会。文章共分为上下两篇,本文为上篇,主要和大家介绍上Kubernetes有什么业务价值,以及什么是“以应用为中心”的 Kubernetes。下篇将跟大家具体分享如何构建“以应用为中心”的 Kubernetes。</p>
<p><strong><strong>关注公众号,回复</strong></strong>“0722”<strong><strong>即可下载 PPT</strong></strong></p>
<p>非常感谢大家来到 CNCF 的直播,我是张磊,阿里云的高级技术专家,Kubernetes 项目资深维护者。同时也是 CNCF 应用交付领域 co-chair。我今天给大家带来的分享主题是《基于 Kubernetes 与 OAM 构建统一、标准化的应用管理平台》。在封面上有个钉钉群组二维码。大家可以通过这个二维码进入线上交流群。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvk7Pu7LACjibshibapmweer8QooolHAJELazlEgJaCtxTJSXlz0ze2ryrlyNs08awKug6GMpxVDqYDg/640?wx_fmt=png" alt=""></p>
<p><strong>上 Kubernetes 有什么业务价值?</strong></p>
<p>今天要演讲的主题是跟应用管理或者说是云原生应用交付是相关的。首先我们想要先回答这么一个问题:为什么我们要基于 Kubernetes 去构建一个应用管理平台?</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvk7Pu7LACjibshibapmweer8QjhcMMlwCRicqK1hhjiaYqVdUD9gSEhsLSDZvdlich8ZFgOWpzic3fomQsg/640?wx_fmt=png" alt=""></p>
<p>上图是一个本质的问题,我们在落地 K8s 经常遇到的一个问题。尤其是我们的业务方会问到这么一个问题,我们上 Kubernetes 有什么业务价值?这时候作为我们 K8s 工程师往往是很难回答的。原因在哪里呢?实际上这跟 K8s 的定位是相关的。K8s 这个项目呢,如果去做一个分析的话,我们会发现 K8s 不是一个 PaaS 或者应用管理的平台。实际上它是一个标准化的能力接入层。什么是能力接入层呢?大家可以看一下下图。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvk7Pu7LACjibshibapmweer8Qibw7FRA1G9hpUenLEOUI0PKbdfr1OReJhtM36pjzQAswxYvM9ib5PdsA/640?wx_fmt=png" alt=""></p>
<p>实际上通过 Kubernetes 对用户暴露出来的是一组声明式 API,这些声明式 API 无论是 Pod 还是 Service 都是对底层基础设施的一个抽象。比如 Pod 是对一组容器的抽象,而 Deployment 是对一组 pod 的抽象。而 Service 作为 Pod 的访问入口,实际上是对集群基础设施:网络、网关、iptables 的一个抽象。Node 是对宿主机的抽象。Kubernetes 还提供了我们叫做 CRD(也就是 Custom Resource)的自定义对象。让你自己能够自定义底层基础设施的一个抽象。</p>
<p>而这些抽象本身或者是 API 本身,是通过另外一个模式叫做控制器(Controller)去实现的。通过控制器去驱动我们的底层基础设施向我的抽象逼近,或者是满足我抽象定义的一个终态。</p>
<p>所以本质来讲,Kubernetes 他的专注点是“如何标准化的接入来自于底层,无论是容器、虚机、负载均衡各种各样的一个能力,然后通过声明式 API 的方式去暴露给用户”。这就意味着 Kubernetes 实际用户不是业务研发,也不是业务运维。那是谁呢?是我们的平台开发者。希望平台开发者能够基于 Kubernetes 再去做上层的框架或者是平台。那就导致了今天我们的业务研发和业务运维对 Kubernetes 直接暴露出来的这一层抽象,感觉并不是很友好。</p>
<p>这里的关键点在于,Kubernetes 对这些基础设施的抽象,跟业务研发和业务运维看待系统的角度是完全不同的。这个抽象程度跟业务研发和业务运维希望的抽象程度也是不一样的。语义完全对不上,使用习惯也是有很大的鸿沟。所以说为了解决这样一个问题,都在思考一些解决方法。怎么能让我 Kubernetes 提供的基础设施的抽象能够满足我业务研发和业务运维的一个诉求呢?怎么能让 Kubernetes 能够成为业务研发和业务运维喜欢的一个平台呢?</p>
<h3 id="方法一把所有人都变成-kubernetes-专家"><strong>方法一:把所有人都变成 Kubernetes 专家</strong><a class="td-heading-self-link" href="#%e6%96%b9%e6%b3%95%e4%b8%80%e6%8a%8a%e6%89%80%e6%9c%89%e4%ba%ba%e9%83%bd%e5%8f%98%e6%88%90-kubernetes-%e4%b8%93%e5%ae%b6" aria-label="Heading self-link"></a></h3>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvk7Pu7LACjibshibapmweer8QicqQTtcXytBOyTdQQe7zLsWCVoicvvVMlZYVzm6Pz1XQ0mc9LD0siavcA/640?wx_fmt=png" alt=""></p>
<p>假如我们所有人都是 Kubernetes 专家,那当然会喜欢 Kubernetes 对我提供的服务,这里给他发个 Kubernetes 的 PhD 博士。这里我强烈推荐阿里云和 CNCF 主办的云原生技术公开课。大家试试学完这门课程后,能不能变成 Kubernetes 专家。</p>
<p>这个方法门槛比较高,因为每个人对于这个系统本身感兴趣程度不太一样,学习能力也不太一样。</p>
<h3 id="方法二构建一个面向用户的应用管理平台"><strong>方法二:构建一个面向用户的应用管理平台</strong><a class="td-heading-self-link" href="#%e6%96%b9%e6%b3%95%e4%ba%8c%e6%9e%84%e5%bb%ba%e4%b8%80%e4%b8%aa%e9%9d%a2%e5%90%91%e7%94%a8%e6%88%b7%e7%9a%84%e5%ba%94%e7%94%a8%e7%ae%a1%e7%90%86%e5%b9%b3%e5%8f%b0" aria-label="Heading self-link"></a></h3>
<p>业界常见的方法,大家会基于 Kubernetes 构建一个面向用户的应用管理平台,或者说是一个 PaaS,有人直接做成一个 Serverless。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvk7Pu7LACjibshibapmweer8QYKexjNJtDyz9V5AMQ1xAh470gONwXHlgQ60WRyt6N5TIWPibNXWdRuQ/640?wx_fmt=png" alt=""></p>
<p>那这个具体是怎么做呢?还是在 Kubernetes 之上,会搭建一个东西叫做上层应用管理平台,这个上层应用平台对业务研发和业务运维暴露出来一个上层的 API。比如说业务研发这一侧,他不太会暴露 Pod,Deployment 这样的抽象。只会暴露出来 CI/CD 流水线。或者说一个应用,WordPress,一个外部网站,暴露出这样一个上层的概念,这是第一个部分。</p>
<p>第二部分,它也会给业务运维暴露出一组运维的 API。比如说:水平扩容,发布策略,分批策略,访问控制,流量配置。这样的话有一个好处,业务研发和业务运维面对的 API 不是 Kubernetes 底层的 API,不是 Node,不是 Service,不是 Deployment,不是我们的 CRD。是这样一组经过抽象经过封装后的 API。这样的业务研发和业务运维用起来会跟他所期望的 Ops 流水线,它所熟悉的使用体检有个天然的结合点。</p>
<p>所以说只有这么做了之后,我们才能够跟我们的业务老大说,Kubernetes 的业务价值来了。实际上业务价值不是在 Kubernetes 这一层,而是在 Kubernetes 往上的这一层&ndash;&quot;<strong>你的解决方案</strong>&quot;。所以说这样的一个系统构建出来之后呢,实际上是对 Kubernetes 又做了一层封装。变成了很多公司都有的,比如说 Kubernetes 应用平台。这是一个非常常见的做法。相比于我们让研发运维变成 Kubernetes 专家来说会更加实际一点。</p>
<p>但是我们在阿里也好,在很多社区的实际场景也好,它往往会伴随着这么一个问题。这个问题是:今天 Kubernetes 的生态是非常非常繁荣的,下图是我在 CNCF 截的图,好几百个项目,几千个可以让我们 Kubernetes 即插即用的能力。比如 istio,KEDA,Promethues 等等都是 Kubernetes 的插件。正是基于这么一个扩展性非常高的声明式 API 体系才会有了这么繁荣的 Kubernetes 生态。所以可以认为 Kubernetes 能力是无限的,非常强大。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvk7Pu7LACjibshibapmweer8QLHL0ibjiauseNYYibibyBwA6lazv8vlz5k9X1Fx9MWCG8FBz3aj2NqpsXQ/640?wx_fmt=png" alt=""></p>
<p>可是这么一个无限能力,如果对接到一个非常传统的,非常经典的一个应用管理平台。比如说我们的 PaaS 上,如 Cloud Foundry。立刻就会发现一个问题,PaaS 虽然对用户提供的是很友好的 API,但是这个 API 本身是有限的,是难以扩展的。比如说 Cloud Foundry 要给用户使用,就有 Buildpack 这么一个概念,而不是 Kubernetes 所有的能力都能给用户去使用。其实几乎所有的 PaaS 都会存在这么一个问题。它往上暴露的是一个用户的API,是不可扩展的,是个有限集。</p>
<p>下面一个非常庞大繁荣的 Kubernetes 生态,没办法直接给用户暴露出去。可能每使用一个插件就要重新迭代开发你的 PaaS,重新交付你的 PaaS。这个是很难接受的。</p>
<h3 id="传统-paas-的能力困境"><strong>传统 PaaS 的“能力困境”</strong><a class="td-heading-self-link" href="#%e4%bc%a0%e7%bb%9f-paas-%e7%9a%84%e8%83%bd%e5%8a%9b%e5%9b%b0%e5%a2%83" aria-label="Heading self-link"></a></h3>
<p>这问题是一个普遍存在的问题,我们叫做传统 PaaS 的“能力困境”。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvk7Pu7LACjibshibapmweer8QoDERgH8CalNEQRVsZ3UdnxkcLXerLqIUdB52bzKpxSVzPEXvaxDFaQ/640?wx_fmt=png" alt=""></p>
<p>本质上来说这个困境是什么意思呢?K8s 生态繁荣多样的应用基础设施能力,与业务开发人员日益增长的应用管理诉求,中间存在一个传统的 PaaS,他就会变成一个瓶颈。K8s 无限的能力无法让你的研发与运维立刻用到。所以传统 PaaS 就会成为一个显而易见的瓶颈。</p>
<p>这样给我带来一个思考:我们能不能抛弃传统 PaaS 的一个做法,基于 K8s 打造高可扩展的应用管理平台。我们想办法能把 K8s 能力无缝的透给用户,同时又能提供传统 PaaS 比较友好的面向研发运维的使用体验呢?</p>
<p>其实可以从另外一个角度思考这个问题:如何基于 K8s 打造高可扩展的应用管理平台,实际上等同于 如何打造一个“以应用为中心的”的 Kubernetes。或者说能不能基于 Kubernetes 去封装下,让它能够像 PaaS 一样,去面向我的实际用户去使用呢?这个就是我们要聊的关键点。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/US10Gcd0tQGY9ddd5GpbmVRuaRfuaESAUBGE7uHX5G0nxxLSub2QTKZdu538V7GaHXS5jsTCebYCUibaHsjg0ow/640?wx_fmt=gif" alt=""></p>
<p><strong>什么是“以应用为中心”的 Kubernetes</strong></p>
<h3 id="特征一通过原生的声明式-api-和插件体系暴露面向最终用户的上层语义和抽象"><strong>特征一:通过原生的声明式 API 和插件体系,暴露面向最终用户的上层语义和抽象</strong><a class="td-heading-self-link" href="#%e7%89%b9%e5%be%81%e4%b8%80%e9%80%9a%e8%bf%87%e5%8e%9f%e7%94%9f%e7%9a%84%e5%a3%b0%e6%98%8e%e5%bc%8f-api-%e5%92%8c%e6%8f%92%e4%bb%b6%e4%bd%93%e7%b3%bb%e6%9a%b4%e9%9c%b2%e9%9d%a2%e5%90%91%e6%9c%80%e7%bb%88%e7%94%a8%e6%88%b7%e7%9a%84%e4%b8%8a%e5%b1%82%e8%af%ad%e4%b9%89%e5%92%8c%e6%8a%bd%e8%b1%a1" aria-label="Heading self-link"></a></h3>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvk7Pu7LACjibshibapmweer8QhBSxNlRvpGWfJDkyx8ftxpO0rswAky1rVmVWho2Ey6RWqE7Dia6LBIw/640?wx_fmt=png" alt=""></p>
<p>我们不是说要在 Kubernetes 上盖一个 PaaS,或者说是盖一个大帽子,不干这件事情。因为 K8s 本身可以扩展,可以写一组 CRD,把我们要的 API 给装上去。比如 CI/CD 流水线,就可以像 Tektong 系统直接使用 pipeline。应用也可以通过某些项目直接暴露出来。运维这一侧的发布扩容等,都可以通过安装一个 Operator 去解决问题。当然也需要一些技术将这些运维策略绑定到应用或者流水线中。</p>
<p>这就是我们第一个点,以应用为中心的 K8s 首先是暴露给用户的语义和 API,而不是非常底层的,比如 Service、Node 或者是 Ingress。可能用户都不知道什么意思,也不知道怎么写的。</p>
<h3 id="特征二上层语义和抽象可插拔可扩展没有抽象程度锁定和任何能力限制"><strong>特征二:上层语义和抽象可插拔,可扩展,没有抽象程度锁定和任何能力限制</strong><a class="td-heading-self-link" href="#%e7%89%b9%e5%be%81%e4%ba%8c%e4%b8%8a%e5%b1%82%e8%af%ad%e4%b9%89%e5%92%8c%e6%8a%bd%e8%b1%a1%e5%8f%af%e6%8f%92%e6%8b%94%e5%8f%af%e6%89%a9%e5%b1%95%e6%b2%a1%e6%9c%89%e6%8a%bd%e8%b1%a1%e7%a8%8b%e5%ba%a6%e9%94%81%e5%ae%9a%e5%92%8c%e4%bb%bb%e4%bd%95%e8%83%bd%e5%8a%9b%e9%99%90%e5%88%b6" aria-label="Heading self-link"></a></h3>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvk7Pu7LACjibshibapmweer8Q0Z4N3HbqpJnstTLAEicg9IR7QVTMWGzN6toXCticVeGHtCxLXUI2kBpQ/640?wx_fmt=png" alt=""></p>
<p>第二个点很重要,上层语义和抽象必须是可插拔的,必须是可扩展的,是无缝兼容利用 K8s 的可扩展能力的。并且也不应该有对抽象程度的锁定。</p>
<p>举个例子:比如一个应用本身既可以是 Deployment,这是一个比较低程度的抽象。也可以是 Knative Service,这是一个相对来说高程度的抽象,相对于 deployment 来说比较简单,只有一个 PodTemplate。甚至可以更简单,可以是一个 Service,或者是个 Function。这个时候抽象程度就很高。如果基于 K8s 做一个以应用为中心的框架的话,它应该是能够暴露工作负载的多种抽象程度的。而不是说单独去使用 Knative,只能暴露出 Knative Service。假如我想使用 Knative 部署一个 Statefulset,这当然是不可以的。抽象程度是完全不一致的。所以我希望这个以应用为中心的 K8s 是没有抽象程度的锁定的。</p>
<p>同时也不应该有能力的限制,什么叫没有能力的限制呢?比如从运维侧举个例子,运维侧有很多很多扩容策略、发布策略等等。如果我想新加一个策略能力,它应该是非常简单的,就像在 K8s 安装一个 Operator 一样非常简单,能 helm insatll 就能搞定,答案是必须的。假如需要添加一个水平扩容,直接 helm install vpa 就能解决。通过这种方式才能做一个以应用为中心的 Kubernetes。</p>
<p>可以看到它跟我们的传统 PaaS 还是有很大区别的,它的可扩展能力非常非常强。它本质上就是一个 K8s,但是它跟专有的 Service,Knative,OpenFaaS 也不一样。它不会把抽象程度锁定到某一种 Workload 上,你的 Workload 是可以随意去定义。运维侧的能力也可以随意可插拔的去定义。这才是我们叫做一个以应用为中心的 Kubernetes。那么这么一个 Kubernetes 怎么做呢?</p>
<p>后续我们将会在下篇文章中详细为大家解读如何构建“以应用为中心”的 Kubernetes?以及构建这么一个以用户为中心的 Kubernetes,需要做几个层级的事情。</p></description></item><item><title>Blog: 如何构建以应用为中心的Kubernetes</title><link>https://desistdaydream.github.io/blog/copy/%E4%BA%91%E5%8E%9F%E7%94%9F/%E5%A6%82%E4%BD%95%E6%9E%84%E5%BB%BA%E4%BB%A5%E5%BA%94%E7%94%A8%E4%B8%BA%E4%B8%AD%E5%BF%83%E7%9A%84Kubernetes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://desistdaydream.github.io/blog/copy/%E4%BA%91%E5%8E%9F%E7%94%9F/%E5%A6%82%E4%BD%95%E6%9E%84%E5%BB%BA%E4%BB%A5%E5%BA%94%E7%94%A8%E4%B8%BA%E4%B8%AD%E5%BF%83%E7%9A%84Kubernetes/</guid><description>
<p><a href="https://mp.weixin.qq.com/s/ql_AIFc0s5HwZgsML63zQA">原文链接</a></p>
<p>本文整理自 2020 年 7 月 22 日《基于 Kubernetes 与 OAM 构建统一、标准化的应用管理平台》主题线上网络研讨会。</p>
<p><strong><strong>关注公众号,回复</strong></strong>“0722”<strong><strong>即可下载 PPT</strong></strong></p>
<p>文章共分为上下两篇。上篇文章《<a href="http://mp.weixin.qq.com/s?__biz=MzUzNzYxNjAzMg==&amp;mid=2247492713&amp;idx=1&amp;sn=63d26542a935a6b3d1cfd7a72f71425b&amp;chksm=fae6efa6cd9166b0c66e73ad47be04d029d40066b7697f2f4c7cd7a53d08ba6e019419166bb8&amp;scene=21#wechat_redirect"><strong>灵魂拷问,上 Kubernetes 有什么业务价值?</strong></a>》,主要和大家介绍了上 Kubernetes 有什么业务价值,以及什么是“以应用为中心”的 Kubernetes。本文为下篇,将跟大家具体分享如何构建“以应用为中心”的 Kubernetes。</p>
<p><strong>如何构建“以应用为中心”的 Kubernetes?</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvlb7BGwRI3ap7CLbL5GOIyZwcdrrveib3FgS5lfWSW255sBic8ichpP7AYUlokiaq0vwibQ6tx6xBFiayjQ/640?wx_fmt=png" alt=""></p>
<p>构建这么一个以用户为中心的 Kubernetes,需要做几个层级的事情。</p>
<h3 id="1-应用层驱动"><strong>1. 应用层驱动</strong><a class="td-heading-self-link" href="#1-%e5%ba%94%e7%94%a8%e5%b1%82%e9%a9%b1%e5%8a%a8" aria-label="Heading self-link"></a></h3>
<p>首先来看最核心的部分,上图中蓝色部分,也就是 Kubernetes。可以在 Kubernetes 之上定义一组 CRD 和 Controller。可以在 CRD 来做用户这一侧的 API,比如说 pipeline 就是一个 API,应用也是一个 API。像运维侧的扩容策略这些都是可以通过 CRD 的方式安装起来。</p>
<h3 id="2-应用层抽象"><strong>2. 应用层抽象</strong><a class="td-heading-self-link" href="#2-%e5%ba%94%e7%94%a8%e5%b1%82%e6%8a%bd%e8%b1%a1" aria-label="Heading self-link"></a></h3>
<p>所以我们的需要解决第一个问题是应用抽象。如果在 Kubernetes 去做应用层抽象,就等同于定义 CRD 和 Controller,所以 Controller 可以叫做应用层的抽象。本身可以是社区里的,比如 Tekton,istio 这些,可以作为你的应用驱动层。这是第一个问题,解决的是抽象的问题。不是特别难。</p>
<h3 id="3-插件能力管理"><strong>3. 插件能力管理</strong><a class="td-heading-self-link" href="#3-%e6%8f%92%e4%bb%b6%e8%83%bd%e5%8a%9b%e7%ae%a1%e7%90%86" aria-label="Heading self-link"></a></h3>
<p>很多功能不是 K8s 提供的,内置的 Controller 还是有限的,大部分能力来自于社区或者是自己开发的 Controller。这时我的集群里面就会安装好多好多插件。如果要构建以应用为中心的 Kubernetes,那我必须能够管理起来这些能力,否则整个集群就会脱管了。用户想要这么一个能力,我需要告诉他有或者是没有。需要暴露出一个 API 来告诉他,集群是否有他需要的能力。假设需要 istio 的流量切分,需要有个接口告诉用户这个能力存不存在。不能指望用户去 get 一下 crd 合不合适,检查 Controller 是否运行。这不叫以应用为中心的 K8s,这叫裸 K8s。</p>
<p>所以必须有个能力,叫做插件能力管理。如果我装了 Tekton,kEDA,istio 这些组件,我必须将这些组件注册到能力注册中心,让用户能够发现这些能力,查询这些能力。这叫做:插件能力管理。</p>
<h3 id="4-用户体验层"><strong>4. 用户体验层</strong><a class="td-heading-self-link" href="#4-%e7%94%a8%e6%88%b7%e4%bd%93%e9%aa%8c%e5%b1%82" aria-label="Heading self-link"></a></h3>
<p>有了应用层驱动,应用层抽象,插件能力管理,我们才能更好地去考虑,如何给用户暴露一个友好的 API 或者是界面出来。有这么几种方式,比如 CLI 客户端命令行工具,或者是一个 Dashboard,又或者是研发侧的 Docker Compose。或者可以让用户写代码,用 python 或者 go 等实现 DSL,这都是可以的。</p>
<p>用户体验层怎么做,完全取决于用户接受什么样的方式。关键点在于以应用为中心的 Kubernetes,UI 层就可以非常方便的基于应用层抽象去做。比如 CLI 就可以直接创建一个流水线和应用,而不是兜兜转转去创建 Deployment 和 Pod,这两个的衔接方式是完全不一样的。pipeline 只需要生成一下就结束了。然后去把 Pod 和 Deployment 组成一个 Pipeline,那这个工作就非常繁琐了。这是非常重要的一点,当你有了应用层驱动,应用层抽象,插件能力管理,再去构建用户体验层就会非常非常简单。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/US10Gcd0tQGY9ddd5GpbmVRuaRfuaESAUBGE7uHX5G0nxxLSub2QTKZdu538V7GaHXS5jsTCebYCUibaHsjg0ow/640?wx_fmt=gif" alt=""></p>
<p><strong>Open Application Model(OAM)</strong></p>
<p>如果想构建一个应用为中心的 Kubernetes,有没有一个标准化的、简单的方案呢?</p>
<p><strong>下面就要为大家介绍:</strong> <strong>Open Application Model(OAM)。</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvlb7BGwRI3ap7CLbL5GOIyZ6yAC7L8wPYdL8WnH4Tr8wlvibeicaQDALk3FpYb6jPIJdgicmmaxkK1yw/640?wx_fmt=png" alt=""></p>
<p>OAM 的本质是帮助你构建一个“以应用为中心“的 Kubernetes 标准规范和框架,相比较前面的方案,OAM 专注于做这三个层次。</p>
<h3 id="1-应用组件-components"><strong>1. 应用组件 Components</strong><a class="td-heading-self-link" href="#1-%e5%ba%94%e7%94%a8%e7%bb%84%e4%bb%b6-components" aria-label="Heading self-link"></a></h3>
<p>第一个叫做应用层抽象,OAM 对用户暴露出自己定义的应用层抽象,第一个抽象叫做 Components。Components 实际上是帮助我们定义 Deployment、StatefulSet 这样的 Workload 的。暴露给用户,让他去定义这些应用的语义。</p>
<h3 id="2-应用特征-traits"><strong>2. 应用特征 Traits</strong><a class="td-heading-self-link" href="#2-%e5%ba%94%e7%94%a8%e7%89%b9%e5%be%81-traits" aria-label="Heading self-link"></a></h3>
<p>第二个叫做应用特征,叫做 Traits。运维侧的概念,比如扩容策略,发布策略,这些策略通过一个叫做 Traits 的 API 暴露给用户。首先 OAM 给你做了一个应用层定义抽象的方式,分别叫做 Components 和 Traits。由于你需要将 Traits 应用特征关联给应用组件 Components,例如 Deployment 需要某种扩容策略或者是发布策略,怎么把他们关联在一起呢?</p>
<h3 id="3-应用配置-application-configuration"><strong>3. 应用配置 Application Configuration</strong><a class="td-heading-self-link" href="#3-%e5%ba%94%e7%94%a8%e9%85%8d%e7%bd%ae-application-configuration" aria-label="Heading self-link"></a></h3>
<p>这个就需要第三种配置叫做 Application Configuration 应用配置。最终这些概念和配置都会变成 CRD,如果你的 K8s 里面安装了 OAM 的 Kubernetes Runtime 组件,那么那就能解析你 CRD 定义的策略和 Workload,最终去交给 K8s 去执行运行起来。就这么一个组件帮助你更好地去定义抽象应用层,提供了几个标准化的方法。</p>
<h3 id="4-能力定义对象-definitions"><strong>4. 能力定义对象 Definitions</strong><a class="td-heading-self-link" href="#4-%e8%83%bd%e5%8a%9b%e5%ae%9a%e4%b9%89%e5%af%b9%e8%b1%a1-definitions" aria-label="Heading self-link"></a></h3>
<p>这些抽象和能力交给 K8s 去处理之后,我这些能力需要的 Controller 插件在哪?有没有 Ready?这些版本是不是已经有了,能不能自动去安装。这是第四个能力了:能力定义对象。这是 OAM 提供的最后一个 API,通过这个 API 可以自己去注册 K8s 所有插件,比如 Tekton、KEDA、istio 等。</p>
<p>把它注册为组件的一个能力,或者是某一个特征。比如说 Flager,可以把它注册为金丝雀发布的能力,用户只要发现这个发布策略存在,说明这个集群支持 Flager,那么他就可以去使用。这就是一个以应用为中心的一个玩法。以用户侧为出发点,而不是以集群侧为出发点,用户侧通过一个上层的 api,特征和组件来去了解他的系统,去操作他的系统。以上就是 OAM 提供的策略和方法。</p>
<p>总结下来就是 OAM 可以通过标准化的方式帮助平台构建者或者开发者去定义用户侧,应用侧的抽象。第二点是提供了插件化能力注册于管理机制。并且有了这些抽象和机制之后,可以非常方便的构建可扩展的 UI 层。这就是 OAM 最核心的功能和价值。</p>
<h3 id="5-oam-会怎样给用户提供一个-api-呢"><strong>5. OAM 会怎样给用户提供一个 API 呢?</strong><a class="td-heading-self-link" href="#5-oam-%e4%bc%9a%e6%80%8e%e6%a0%b7%e7%bb%99%e7%94%a8%e6%88%b7%e6%8f%90%e4%be%9b%e4%b8%80%e4%b8%aa-api-%e5%91%a2" aria-label="Heading self-link"></a></h3>
<h3 id="1components"><strong>1)Components</strong><a class="td-heading-self-link" href="#1components" aria-label="Heading self-link"></a></h3>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvlb7BGwRI3ap7CLbL5GOIyZicrjzfqhULmjvxiciaXF9tyibpvKn9priauiaUEiaEvJRLtNia1Saib4TLsggjA/640?wx_fmt=png" alt=""></p>
<p>Component 是工作负载的版本化定义,例如上图中创建一个 Component,实际上就是创建一个 Deployment。这样一个 Component 交给 K8s 之后,首先会创建一个 Component 来管理这个 Workload,当你修改 Component 之后就会生成一个对应版本的 deployment。这个 Component 实际上是 Deployment 的一个模板。比如我把 image 的版本修改一下,这个操作就会触发 OAM 插件,生成一个新的版本的 Deployment,这是第一个点。其实就版本化管理机制去管理 Component。</p>
<p>第二点是 Workload 部分完全是自定义的,或者是是可插拔的。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvlb7BGwRI3ap7CLbL5GOIyZQDmYSgw2tyF9jpq4ibd0tPkpLG6V18L6VSOFpwPOYXK5NOosH3icCCYQ/640?wx_fmt=png" alt=""></p>
<p>今天可以定义为 Deployment,明天可以定义为一个非常简单的版本。也就是说我 Components 的抽象程度完全取决于用户自己决定的。后期也可以改成 Knative Service,甚至改成一个 Open PaaS。所以说在 Components 的 Workload 部分你可以自由的去定义自己的抽象。只要你提前安装了对应 CRD 即可,这是一个非常高级的玩法。</p>
<p>此外在 OAM 中,”云服务“也是一种 Workload, 只要你能用 CRD 定义你的云服务,就可以直接在 OAM 中定义为一个应用所依赖的组件。比如上图中的redis实际上是阿里云的 Redis 服务,大概是这么一个玩法。</p>
<h3 id="2trait-和-application-configuration"><strong>2)Trait 和 Application Configuration</strong><a class="td-heading-self-link" href="#2trait-%e5%92%8c-application-configuration" aria-label="Heading self-link"></a></h3>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvlb7BGwRI3ap7CLbL5GOIyZV3ozMFKxZb0rx5OjNF4JK3iacnBZavqticlsEhib8ETcvlUIw6doE5dibw/640?wx_fmt=png" alt=""></p>
<p>首先 Trait 是声明式运维能力的描述,其实就是 Kubernetes API 对象。任何管理和运维 Workload 的组件和能力,都可以以这种 CER 的方式定义为一个 Trait。所以像 HPA,API gateway,istio 里面的 Virtual Services 都是 Trait。</p>
<p>Application Configuration 就像是一个信封,将 Traits 绑定给 Component,这个是显式绑定的。OAM 里面不建议去使用 Label 这样的松耦合的方式去关联你的工作负载。建议通过这种结构化的方式,通过 CRD 去显式的绑定你的特征和工作负载。这样的好处是我的绑定关系是可管理的。可以通过 kubectl get 看到这个绑定关系。作为管理员或者用户,就非常容易的看到某一个组件绑定的所有运维能力有哪些,这是可以直接展示出来的,如果通过 label 是很难做到的。同时 Label 本身有个问题是,本身不是版本化的,不是结构体,很难去升级,很难去扩展。通过这么结构化定义,后面的升级扩展将会变得非常简单。 </p>
<p>在一个用户配置里面,可以关联多个 Components。它认为一个应用运行所需要的所有组件和所依赖的运维能力,都应该定义为一个文件叫做 ApplicationConfiguration。所以在任何环境,只要拥有这个文件,提交之后,这个应用就会生效了。OAM 是希望能够提供一个自包含的应用声明方式。</p>
<h3 id="3definition-object"><strong>3)Definition Object</strong><a class="td-heading-self-link" href="#3definition-object" aria-label="Heading self-link"></a></h3>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvlb7BGwRI3ap7CLbL5GOIyZhAQ2EeRQcL8DztTJdZLrSbV4AuicsTFnmpXYokfia6jA2Fjjj7usHpAw/640?wx_fmt=png" alt=""></p>
<p>除此之外,还提到了对应管理员提供了 Definition Object,这是用来注册和发现插件化能力的 API 对象。</p>
<p>比如我想讲 Knative Service 定义为平台支持的一种工作负载,如上图只需要简单的写一个文件即可。其中在 definitionRef 中引用 service.serving.knative.dev 即可。这样的好处就是可以直接用 kubectl get Workload 查看 Knative Service 的 Workload。所以这是一个用来注册和发现插件化能力的机制,使得用户非常简单的看到系统中当前有没有一个工作负载叫做 Knative Service。而不是让用户去看 CRD,看插件是否安装,看 Controller 是否 running,这是非常麻烦的一件事情。所以必须有这么一个插件注册和发现机制。</p>
<p>这一部分还有其他额外的能力,可以注册 Trait,并且允许注册的 Trait-A 和 Trait-B 是冲突的。这个信息也能带进去,这样部署的时候检查到 A 和 B 是冲突的,会产生报错信息。否则部署下去结果什么都不知道,两个能力是冲突的,赶紧删了回滚重新创建。OAM 在注册的时候就会暴露出来运维能力的冲突,这也是靠 Definition 去做的。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/yvBJb5Iiafvlb7BGwRI3ap7CLbL5GOIyZcuSZtGhUKRMs2jjmLD5PiaO72IKtaxbfibNcWGsliawhtqUKWXSrS3Vpg/640?wx_fmt=jpeg" alt=""></p>
<p>除此之外,OAM 的 model 这层其他的一些附加能力,能够让你定义更为复杂的应用。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/US10Gcd0tQGY9ddd5GpbmVRuaRfuaESAUBGE7uHX5G0nxxLSub2QTKZdu538V7GaHXS5jsTCebYCUibaHsjg0ow/640?wx_fmt=gif" alt=""></p>
<p><strong>总结</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvlb7BGwRI3ap7CLbL5GOIyZQ4fuTxibpUFIWWVFhEoPu3GhZJT3JCk3PICeTxuk7m0xlgFOnMnJ3Lw/640?wx_fmt=png" alt=""></p>
<p>前面我们提到很多企业等等都在基于 Kubernetes 去构建一个上层应用管理平台。Kubernetes 实际上是面向平台开发者,而不是面向研发和应用运维的这么一个项目。它天生就是这么设计的,所以说需要基于 Kubernetes 去构建应用管理平台。去更好的服务研发和运维,这也是一个非常自然的选择。不是说必须使用 K8s 去服务你的用户。如果你的用户都是 K8s 专家,这是没问题的。如果不是的话,你去做这样一个应用平台是非常自然的事情。</p>
<p>但是我们不想在 K8s 之前架一个像 Cloud Foundry 传统的 PaaS。因为它会把 K8s 的能力完全遮住。它有自己的一套 API,自己的理念,自己的模型,自己的使用方式。跟 Kubernetes 都是不太一样的,很难把 Kubernetes 的能力给暴露出去。这是经典 PaaS 的一个用法,但是我们不想要这么一个理念。我们的目标是既能给用户提供一个使用体验,同时又能把 Kubernetes 的能力全部发挥出来。并且使用体验跟 Kubernetes 是完全一致的。OAM 本质上要做的是面向开发和运维的,或者说是面向以应用为中心的Kubernetes。</p>
<p>所以今天所介绍的 OAM 是一个统一、标准、高可扩展的应用管理平台,能够以应用为中心的全新的 Kubernetes,这是今天讨论的一个重点。OAM 这个项目就是支撑这种理念的核心依赖和机制。简单地来说 OAM 能够让你以统一的,标准化的方式去做这件事情。比如标准化定义应用层抽象,标准化编写底层应用驱动,标准化管理 K8s 插件能力。</p>
<p>对于平台工程师来说,日常的工作能不能以一个标准化的框架或者依赖让平台工程师更简单更快的做这件事情。这就是 OAM 给平台工程师带来的价值。当然它也有些额外的好处,基于 OAM 暴露出来的新的 API 之后,你上层的UI构建起来会非常简单。</p>
<p>你的 OAM 天然分为两类,一类叫做工作负载,一类叫做运维特征。所以你的 UI 这层可以直接去对接了,会减少很多前端的工作。如果基于 CI/CD 做 GitOps / 持续集成发现也会变得非常简单。因为它把一个应用通过自包含的方式给定义出来了,而不是说写很多个 yaml 文件。并且这个文件不仅自包含了工作负载,也包括了运维特征。所以创建好了这个文件往 Kubernetes 中提交,这个应用要做金丝雀发布或者是蓝绿发布,流量控制,全部是清清楚楚的定义在这个应用配置文件里面的。因为 GitOps 也好,持续集成也好,是不想管你的 pod 或者是 Deployment 怎么生成的,这个应用怎么运维,怎么 run 起来,还是要靠 Kubernetes 插件或者内置能力去做的。这些能力都被定义到一个自包含的文件,适用于所有集群。所以这就会使得你的 GitOps 和持续集成变得简单。</p>
<p>以上就是 OAM 给平台工程师带来的一些特有的价值。简单来说是统一、标准的 API,区分研发和运维策略,让你的 UI 和 GitOps 特别容易去构建。另一点是向下提供了高可扩展的管理 K8s 插件能力。这样的系统真正做到了标准,自运维,一个以应用为中心和用户为中心的 Kubernetes 平台。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/US10Gcd0tQGY9ddd5GpbmVRuaRfuaESAUBGE7uHX5G0nxxLSub2QTKZdu538V7GaHXS5jsTCebYCUibaHsjg0ow/640?wx_fmt=gif" alt=""></p>
<p><strong>OAM 社区</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/yvBJb5Iiafvlb7BGwRI3ap7CLbL5GOIyZJ2k4MkCDLm5ggusQFO3JsudicVZT9OUe43OHSsLD8TRQYEVSDh4blBg/640?wx_fmt=png" alt=""></p>
<p>上面最后希望大家踊跃加入 OAM 社区,参与讨论。上图中有钉钉群二维码,目前人数有几千人,讨论非常激烈,我们会在里面讨论 GitOps,CI/CD,构建 OAM 平台等等。OAM 也有亚太地区的周会,大家可以去参加。上面的链接是开源项目地址,将这个安装到 Kubernetes 中就可以使用上面我们说的这些能力了。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_gif/US10Gcd0tQGY9ddd5GpbmVRuaRfuaESAUBGE7uHX5G0nxxLSub2QTKZdu538V7GaHXS5jsTCebYCUibaHsjg0ow/640?wx_fmt=gif" alt=""></p>
<p><strong>QA 环节</strong></p>
<p><strong>Q1:</strong> 例子中提问到了 Function 的例子,是否可以理解为 Serverless 或者是 PaaS?</p>
<p><strong>A1</strong>**:** 这样理解是没错的,可以理解为阿里云的一个 Function,或者是 Knative Service。</p>
<p><strong>Q2:</strong> 有没有可以让我自由定义出相应的规则这种规范?</p>
<p><strong>A2:</strong> 有的,在 OAM 里面有个规范,叫做 spec。spec 里面有提交容器化的规范。后面会增加更多抽象的规范。当然也分类别,有一些是非常标准化的,需要严格遵守。有一些是比较松的,可以不用严格遵守。</p>
<p><strong>Q3:</strong> docker-compose 的例子可否再谈谈?</p>
<p><strong>A3:</strong> 本次 ppt 中没有 docker-compose 的例子,但是这个其实很容易去理解,因为 OAM 将 Kubernetes API 分为两类,一个叫做 Components,一个叫T raits。有这么一个 Componets 文件,就可以直接映射 OAM 的概念,docker-compose 中有个概念叫做 Service,其实就是对应了 OAM 中的 Component。这完全是一对一对应关系。Service 下面有个 Deployment,有个部署策略,其实对应的就是 OAM 的 Trait。</p>
<p><strong>Q4:</strong> 定义阿里云的 redis 是否已经实现了?</p>
<p><strong>A4:</strong> 已经实现了,但是功能有限。内部已经实现了一个更强大的功能,通过 OAM 将阿里云的所有资源给创建起来。目前这个是在 Crossplane 去做的。但是内部更完整的实现还没有完全的放出去。我们还在规划中,希望通过一个叫做 Alibaba Opreator 的方式暴露出去。</p>
<p><strong>Q5:</strong> 是否可以理解 OAM 通过管理元数据通过编写 CRD 来打包 Components 和 Traits。</p>
<p><strong>A5:</strong> 可以说是对的。你把自己的 CRD 也好,社区里面的 CRD 也好,稍微做个分类或者封装,暴露给用户。所以对于用户来说只要理解两个概念——Components 和 Traits。Components 里面的内容是靠你的 CRD 来决定的,所以说这是一个比较轻量级的抽象。</p>
<p><strong>Q6:</strong> 假设 Components 有四个,Traits 有五个,是否可以理解为可封装能力有 20 项。</p>
<p><strong>A6:</strong> 这个不是这么算的,不管有多少 Components 和 Trait,最终有几个能力取决于你注册的实际 CRD。Components 和 Traits 与背后的能力是解耦开的。</p>
<p><strong>Q7:</strong> OAM 能使用 Kustomize 生成么?</p>
<p><strong>A7:</strong> 当然可以了,Kustomize 使一个 yaml 文件操作工具。你可以用这个工具生成任何你想要的 yaml 文件,你也可以用其他的,比如 google 的另一个项目叫 kpt,比如你用 DSL,json。所有可以操作 yaml 文件的工具都可以操作 OAM 文件,OAM 的 yaml 文件跟正常的 K8s 中的 yaml 没有任何区别。在 K8s 看来 OAM 无非就是一个 CRD。</p>
<p><strong>Q8:</strong> OAM 是否可以生产可用?</p>
<p><strong>A8:</strong> 这里面分几个点,OAM 本身分两个部分。第一部分是规范,是处于 alpha 版本,计划在 2020 年内发布 beta 版本。beta 就是一个稳定版本,这是一个比较明确的计划。现在的 spec 是有可能会变的,但是有另外一个版本叫做 oam-kubernetes-runtime 插件,这是作为独立项目去运营的,计划在 Q3 发布稳定版本。即使我的 spec 发生的改变,但是插件会做向下兼容,保证 spec 变化不会影响你的系统,我们的 runtime 会提前发布稳定版本,应该是比较快的。如果构建平台化建议优先使用 runtime。</p>
<p><strong>Q9:</strong> OAM 有没有稳定性考虑?比如说高可用。</p>
<p><strong>A9:</strong> 这个是有的,目前 runtime 这个项目就在做很多稳定性的东西,这是阿里内部和微软内部的一个诉求。这块都是在做,肯定是有这方面考虑的,包括边界条件的一个覆盖。</p>
<p><strong>Q10:</strong> 可不可介绍下双十一的状态下,有多少个 Pod 在支持?</p>
<p><strong>A10:</strong> 这个数量会比较大,大概在十几万这样一个规模,应用容器数也是很多的。这个对大家的参考价值不是很大,因为阿里的架构和应用跟大多数同学看到的是不太一样的,大多数是个单元化的框架,每个应用拆分的微服务非常非常细。pod 数和容器数都是非常多的。</p>
<p><strong>Q11:</strong> 目前 OAM 只有阿里和微软,以后像 google 这些大厂会加入么?</p>
<p><strong>A11:</strong> 一定会的,接下来的计划会引入新的合作方。目前 google 和 aws 都对 OAM 有一些社区的支持。本身作为云原生的一个规范,也是有一些想法的。在初期的时候,大厂加入的速度会比较慢,更希望的是用户使用起来。大厂并不一定是 OAM 的主要用户,他们更多的是商业考虑。</p>
<p><strong>Q12:</strong> OAM 是否会关联 Mesh?</p>
<p><strong>A12:</strong> 一定会的,但是并不是说直接 Mesh 一个核心能力,更多的说作为 OAM trait 使用,比如描述一个流量的拓扑关系。</p>
<p><strong>Q13:</strong> OAM 的高可用方案?</p>
<p><strong>A13:</strong> OAM 本身就是个无状态服务,本身的高可用方案不是很复杂。</p>
<p><strong>Q14:</strong> OAM 考虑是单集群还是多集群?</p>
<p><strong>A14:</strong> 目前是单集群,但是我们马上也会发布多集群的模型,在阿里内部已经是多集群模型。简单来说多集群是两层模型。多集群的概念是定义在 Scope 里面的,通过 Scope 来决定 Workload 或者是 Components 放到哪个集群里面。我们会在社区尽快放出来。</p></description></item><item><title>Blog: 硬核致敬Linux !30岁生日快乐!</title><link>https://desistdaydream.github.io/blog/copy/cE4x63tYxoqrDinifeWqeg/</link><pubDate>Thu, 26 Aug 2021 00:00:00 +0000</pubDate><guid>https://desistdaydream.github.io/blog/copy/cE4x63tYxoqrDinifeWqeg/</guid><description>
<p>原文链接:<a href="https://mp.weixin.qq.com/s/cE4x63tYxoqrDinifeWqeg">https://mp.weixin.qq.com/s/cE4x63tYxoqrDinifeWqeg</a></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/Pn4Sm0RsAuhH4SOtTAkhF5RQnT4PAWdG2NT9Smu9eqEV5PAwKq3PbC6iagpqsfWz47RFLIZibibcIDAn3IyVS0ahw/640?wx_fmt=png" alt=""></p>
<p>1991年8月25日,21岁的Linus Torvalds(以下简称Linus)做了一个免费的操作系统“Linux”,并在这一天向外界公布这个由“业余爱好”主导的个人项目;如今,全球超级计算机500强和超过70%的智能手机都在运行Linux,因此,8月25日也被许多Linux的爱好者视为Linux真正的诞生日期。</p>
<h1 id="你好">你好<a class="td-heading-self-link" href="#%e4%bd%a0%e5%a5%bd" aria-label="Heading self-link"></a></h1>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgH5rcBjAWV0lF8QBtlXSJgRrJqBP90P2rfTd8WpVRAtyzqxhbXd6QnNg/640?wx_fmt=png" alt=""></p>
<p>30 年前,Linus Torvalds 首次发布 Linux 内核时还是赫尔辛基大学的一名 21 岁学生。他的声明是这样开始的,“我正在做一个(免费的)操作系统(只是一个爱好,不会很大和专业&hellip;&hellip;)”。三十年后,排名前 500 的超级计算机都在运行 Linux,所有智能手机的 70% 以上都是如此。Linux 显然既庞大又专业。</p>
<p>三十年来,Linus Torvalds 领导了 Linux 内核开发,激励了无数其他开发人员和开源项目。2005 年,Linus 还创建了 Git来帮助管理内核开发过程,此后它成为最受欢迎的版本控制系统,受到无数开源和专有项目的信赖。</p>
<h3 id="linux历史">Linux历史<a class="td-heading-self-link" href="#linux%e5%8e%86%e5%8f%b2" aria-label="Heading self-link"></a></h3>
<p><strong>OS史前历史</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHE70XibBtS3DT8Nf3r5k48PGFo8ON6CPEsuyBOxIia8eLIQOuuz6JV1aA/640?wx_fmt=png" alt=""></p>
<p><strong>Linux的历史</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHRicpx9aScqDmIdIib1M2UdibnVGVHJoTR5j94qiaCosHsT4G1XlPL1vYzA/640?wx_fmt=png" alt=""></p>
<h3 id="linux系统">Linux系统<a class="td-heading-self-link" href="#linux%e7%b3%bb%e7%bb%9f" aria-label="Heading self-link"></a></h3>
<p><strong>Linux系统软件架构</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgH1QmL07tiarw5K00x2LuwJEaRFCR4eev2O1DI1uEd6rZPOKnNZHzv5Eg/640?wx_fmt=jpeg" alt=""></p>
<p>Linux系统由硬件、kernel、系统调用、shell、c库、应用程序组成,架构层次分明,Linux内的各种层功能独立,程序在用户空间和内核空间之间的分离,能支持更多应用。</p>
<p>| 用户模态 | <strong>用户应用</strong> | 例如:Bash,LibreOffice,GIMP,Blender,0 A.D.,Mozilla Firefox等 |
| 低层系统构件 | <strong>系统守护进程</strong>:<br>
systemd,runit,logind,networkd,PulseAudio等 | <strong>窗口系统</strong>:<br>
X11,Wayland,SurfaceFlinger(Android) | <strong>其他库</strong>:<br>
GTK+, Qt, EFL, SDL, SFML, FLTK, GNUstep等 | <strong>图形</strong>:<br>
Mesa,AMD Catalyst等 |
| <strong>C标准库</strong> | open(),exec(),sbrk(),socket(),fopen(),calloc(),&hellip; (直到2000个子例程)<br>
glibc目标为POSIX/SUS兼容,musl和uClibc目标为嵌入式系统,bionic为Android而写等 |
| 内核模态 | <strong>Linux内核</strong> | stat, splice, dup, read, open, ioctl, write, mmap, close, exit等(大约380个系统调用)<br>
Linux内核系统调用接口(SCI,目标为POSIX/SUS兼容) |
| 进程调度子系统 | IPC子系统 | 内存管理子系统 | 虚拟文件子系统 | 网络子系统 |
| 其他构件:ALSA,DRI,evdev,LVM,device mapper,Linux Network Scheduler,Netfilter<br>
Linux安全模块:SELinux,TOMOYO,AppArmor, Smack |
| 硬件(CPU,内存,数据存储设备等。) |</p>
<p><strong>Linux内核代码架构</strong></p>
<p><strong><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHSXWCabp9jDHopYuOGHSSq3HgXcQKLkWedlzYiaNOBStEEod9YkB8JAw/640?wx_fmt=jpeg" alt=""></strong></p>
<p>Linux代码包含核心几个子系统,比如<strong>内存子系统</strong>,<strong>I/O子系统</strong>,<strong>CPU(调度)子系统</strong>,<strong>设备驱动子系统</strong>,<strong>网络子系统</strong>,<strong>虚拟文件子系统</strong>等。这里简单介绍一些比较重要的子系统。</p>
<h3 id="调度子系统">调度子系统<a class="td-heading-self-link" href="#%e8%b0%83%e5%ba%a6%e5%ad%90%e7%b3%bb%e7%bb%9f" aria-label="Heading self-link"></a></h3>
<p><strong>进程调度</strong>是Linux内核中最重要的子系统,它主要提供对CPU的访问控制。因为在计算机中,CPU资源是有限的,而众多的应用程序都要使用CPU资源,所以需要“进程调度子系统”对CPU进行调度管理。</p>
<p><strong>进程调度子系统</strong>包括4个子模块(见下图),它们的功能如下:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHzhTgCrceib1hkg2RZuwSgf86iaN2JfyHMptZFZGdw0fhQW1hNLgMBlHA/640?wx_fmt=jpeg" alt=""></p>
<ol>
<li>
<p>Scheduling Policy,实现进程调度的策略,它决定哪个(或哪几个)进程将拥有CPU。</p>
</li>
<li>
<p>Architecture-specific Schedulers,体系结构相关的部分,用于将对不同CPU的控制,抽象为统一的接口。这些控制主要在suspend和resume进程时使用,牵涉到CPU的寄存器访问、汇编指令操作等。</p>
</li>
<li>
<p>Architecture-independent Scheduler,体系结构无关的部分。它会和“Scheduling Policy模块”沟通,决定接下来要执行哪个进程,然后通过“Architecture-specific Schedulers模块”resume指定的进程。</p>
</li>
<li>
<p>System Call Interface,系统调用接口。进程调度子系统通过系统调用接口,将需要提供给用户空间的接口开放出去,同时屏蔽掉不需要用户空间程序关心的细节。</p>
</li>
</ol>
<h3 id="内存子系统">内存子系统<a class="td-heading-self-link" href="#%e5%86%85%e5%ad%98%e5%ad%90%e7%b3%bb%e7%bb%9f" aria-label="Heading self-link"></a></h3>
<p><strong>内存管理</strong>同样是Linux内核中最重要的子系统,它主要提供对内存资源的访问控制。Linux系统会在硬件物理内存和进程所使用的内存(称作虚拟内存)之间建立一种映射关系,这种映射是以进程为单位,因而不同的进程可以使用相同的虚拟内存,而这些相同的虚拟内存,可以映射到不同的物理内存上。</p>
<p><strong>内存管理子系统</strong>包括3个子模块(见下图),它们的功能如下:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHNY1OZLaKPcddjClNYLNRmEPuUt7fQf7iafZ7eJtrP46fWqws0wD5yww/640?wx_fmt=jpeg" alt=""></p>
<ol>
<li>
<p>Architecture Specific Managers,体系结构相关部分。提供用于访问硬件Memory的虚拟接口。</p>
</li>
<li>
<p>Architecture Independent Manager,体系结构无关部分。提供所有的内存管理机制,包括:以进程为单位的memory mapping;虚拟内存的Swapping。</p>
</li>
<li>
<p>System Call Interface,系统调用接口。通过该接口,向用户空间程序应用程序提供内存的分配、释放,文件的map等功能。</p>
</li>
</ol>
<h3 id="虚拟文件子系统virtual-filesystem-vfs">虚拟文件子系统(Virtual Filesystem, VFS)<a class="td-heading-self-link" href="#%e8%99%9a%e6%8b%9f%e6%96%87%e4%bb%b6%e5%ad%90%e7%b3%bb%e7%bb%9fvirtual-filesystem-vfs" aria-label="Heading self-link"></a></h3>
<p>传统意义上的文件系统,是一种存储和组织计算机数据的方法。它用易懂、人性化的方法(文件和目录结构),抽象计算机磁盘、硬盘等设备上冰冷的数据块,从而使对它们的查找和访问变得容易。因而文件系统的实质,就是“存储和组织数据的方法”,文件系统的表现形式,就是“从某个设备中读取数据和向某个设备写入数据”。</p>
<p>随着计算机技术的进步,存储和组织数据的方法也是在不断进步的,从而导致有多种类型的文件系统,例如FAT、FAT32、NTFS、EXT2、EXT3等等。而为了兼容,操作系统或者内核,要以相同的表现形式,同时支持多种类型的文件系统,这就延伸出了**虚拟文件系统(VFS)**的概念。VFS的功能就是管理各种各样的文件系统,屏蔽它们的差异,以统一的方式,为用户程序提供访问文件的接口。</p>
<p>我们可以从磁盘、硬盘、NAND Flash等设备中读取或写入数据,因而最初的文件系统都是构建在这些设备之上的。这个概念也可以推广到其它的硬件设备,例如内存、显示器(LCD)、键盘、串口等等。我们对硬件设备的访问控制,也可以归纳为读取或者写入数据,因而可以用统一的文件操作接口访问。Linux内核就是这样做的,除了传统的磁盘文件系统之外,它还抽象出了设备文件系统、内存文件系统等等。这些逻辑,都是由VFS子系统实现。</p>
<p><strong>VFS子系统</strong>包括6个子模块(见下图),它们的功能如下:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHxGOz2c8yZ6yxrrHeibxu3ac8nibP9KGUkVkrXPmgIH9iasFgLjaofdWqA/640?wx_fmt=jpeg" alt=""></p>
<ol>
<li>
<p>Device Drivers,设备驱动,用于控制所有的外部设备及控制器。由于存在大量不能相互兼容的硬件设备(特别是嵌入式产品),所以也有非常多的设备驱动。因此,Linux内核中将近一半的Source Code都是设备驱动,大多数的Linux底层工程师(特别是国内的企业)都是在编写或者维护设备驱动,而无暇估计其它内容(它们恰恰是Linux内核的精髓所在)。</p>
</li>
<li>
<p>Device Independent Interface, 该模块定义了描述硬件设备的统一方式(统一设备模型),所有的设备驱动都遵守这个定义,可以降低开发的难度。同时可以用一致的形势向上提供接口。</p>
</li>
<li>
<p>Logical Systems,每一种文件系统,都会对应一个Logical System(逻辑文件系统),它会实现具体的文件系统逻辑。</p>
</li>
<li>
<p>System Independent Interface,该模块负责以统一的接口(快设备和字符设备)表示硬件设备和逻辑文件系统,这样上层软件就不再关心具体的硬件形态了。</p>
</li>
<li>
<p>System Call Interface,系统调用接口,向用户空间提供访问文件系统和硬件设备的统一的接口。</p>
</li>
</ol>
<h3 id="网络子系统net">网络子系统(Net)<a class="td-heading-self-link" href="#%e7%bd%91%e7%bb%9c%e5%ad%90%e7%b3%bb%e7%bb%9fnet" aria-label="Heading self-link"></a></h3>
<p><strong>网络子系统</strong>在Linux内核中主要负责管理各种网络设备,并实现各种网络协议栈,最终实现通过网络连接其它系统的功能。在Linux内核中,网络子系统几乎是自成体系,它包括5个子模块(见下图),它们的功能如下:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHy9IqgNnoDPLwVHDKqVBzD6iaJtzNk7wm3h9aSn1Nf6xOsNM870ScwEA/640?wx_fmt=jpeg" alt=""></p>
<ol>
<li>
<p>Network Device Drivers,网络设备的驱动,和VFS子系统中的设备驱动是一样的。</p>
</li>
<li>
<p>Device Independent Interface,和VFS子系统中的是一样的。</p>
</li>
<li>
<p>Network Protocols,实现各种网络传输协议,例如IP, TCP, UDP等等。</p>
</li>
<li>
<p>Protocol Independent Interface,屏蔽不同的硬件设备和网络协议,以相同的格式提供接口(socket)。</p>
</li>
<li>
<p>System Call interface,系统调用接口,向用户空间提供访问网络设备的统一的接口。</p>
</li>
</ol>
<p>Linux内核版本时间线:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHRqUJPPCSOutrOnet4ICAkEbUHf9LLgGX4rorHj7nXDOcyTvLEJ29Bg/640?wx_fmt=png" alt=""></p>
<p><strong>Linux内核支持各种硬件架构</strong></p>
<p><strong>Linux内核</strong>最成功的地方之一就是支持各种硬件架构,为软件提供了公共的平台:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHlp75WAFV1ibBJ0IAN2BVxzKia8ET9rFicziaQxtA0sEl0icYnHula6HzsIQ/640?wx_fmt=png" alt=""></p>
<p>基于Linux的系统是一个模块化的类Unix操作系统。<strong>Linux操作系统</strong>的大部分设计思想来源于20世纪70年代到80年代的Unix操作系统所建立的基本设计思想。Linux系统使用宏内核,由Linux内核负责处理进程控制、网络,以及外围设备和文件系统的访问。在系统运行的时候,设备驱动程序要么与内核直接整合,要么以加载模块形式添加。</p>
<p><strong>Linux具有设备独立性</strong>,它内核具有高度适应能力,从而给系统提供了更高级的功能。GNU用户界面组件是大多数Linux操作系统的重要组成部分,提供常用的C函数库,Shell,还有许多常见的Unix实用工具,可以完成许多基本的操作系统任务。大多数Linux系统使用的图形用户界面建立在X窗口系统之上,由X窗口(XWindow)系统通过软件工具及架构协议来建立操作系统所用的图形用户界面.</p>
<p><strong>基于Linux内核各种衍生OS系统</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHiaA5ia2r6z3iarNHHZN7gHOISWONZ0vfBibAz5wm0tJfxaBao2KOF4fM8Q/640?wx_fmt=png" alt=""></p>
<p>各种发行版本</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHJQp1UUv39Vx04CB4W3DSdh2twrDS7kZV01ygjFXbyeUCfg92kJpiaqA/640?wx_fmt=png" alt=""></p>
<p>当前比较流行发行版是:<strong>Debian</strong>、<strong>Ubuntu</strong>、<strong>Fedora</strong>、<strong>CentOS</strong>、<strong>Arch Linux</strong>和<strong>openSUSE</strong>等,每个发行版都有自己优势地方,都有一批忠实用户。</p>
<p><strong>基于Linux内核著名OS</strong></p>
<p><strong>Android</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHwsGP7L9tNBESaK6GlTHaZWjXcial9ia9bWDQoXqPDUSh4rxfYtCOG1XA/640?wx_fmt=png" alt=""></p>
<p><strong>Android</strong>(读音:英:[&lsquo;ændrɔɪd],美:[ˈænˌdrɔɪd]),中文用户多以非官方名称“安卓”称之,是一个基于Linux内核与其他开源软件的开放源代码的移动操作系统,Android的内核是根据Linux内核的长期支持的分支,具有典型的Linux调度和功能。截至2018年,Android的目标是Linux内核的4.4、4.9或是4.14版本。</p>
<p><strong>ChromeOS</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHGtbgyZUUeFSicfcZCeRpTGicmJI5vzDibiaCibBfoA06demaPcn1iaVficviag/640?wx_fmt=png" alt=""></p>
<p><strong>Chrome OS</strong> 是由Google设计基于Linux内核的操作系统,并使用Google Chrome浏览器作为其主要用户界面。因此,Chrome OS主要支持Web应用程序[6],2016年起开始陆续兼容Android应用程序(可通过Google Play商店下载)和Linux应用程序。</p>
<p><strong>鸿蒙OS</strong></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHdmDMzZHEZR94PaMFm9CYibMfvicxDs2ULz9jQJVZKUQNOtAUduCqRJUA/640?wx_fmt=png" alt=""></p>
<p><strong>鸿蒙</strong>(<strong>HarmonyOS</strong>,开发代号Ark[1])是华为自2012年开发的一款可兼容Android应用程序的跨平台分布式操作系统[2]。系统性能包括利用“分布式”技术将各款设备融合成一个“超级终端”,便于操作和共享各设备资源。[3][4][5]系统架构支持多内核,包括Linux内核、LiteOS和鸿蒙微内核,可按各种智能设备选择所需内核,例如在低功耗设备上使用LiteOS内核。[6][7]2019年8月华为发布首款搭载鸿蒙操作系统的产品“荣耀智能屏”,之后于2021年6月发布搭载鸿蒙操作系统的智能手机、平板电脑和智能手表。</p>
<p>Linux 内核是最大且变动最快的开源项目之一,它由大约 53,600 个文件和近 2,000 万行代码组成。在全世界范围内超过 15,600 位程序员为它贡献代码,Linux 内核项目的维护者使用了如下的协作模型。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHHIWib5fYsnBg0ziccyM2IEj3VRhGUQlkzPSyDpQ5cicLo148yoJ8z33LQ/640?wx_fmt=jpeg" alt=""></p>
<p>如果你有<strong>深入linux内核的激情</strong>和<strong>极客精神</strong>,可以为Linux项目贡献源码,具体如何提交第一个补丁,可以详细阅读下面文章,这里篇幅有限不展开:</p>
<p><a href="https://opensource.com/article/18/8/first-linux-kernel-patch">https://opensource.com/article/18/8/first-linux-kernel-patch</a></p>
<p>Linux 开源代码仓库:</p>
<p><a href="https://github.com/torvalds/linux">https://github.com/torvalds/linux</a></p>
<p>提交给kernel的补丁,刚开始可能不需要高深的技术,比如这个补丁,可以 是简单的对于已有内容的格式或拼写错误的修正,比如这个来自4岁小朋友的补丁:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHv1787MroX0hXfDbFQCZhqCW68Otw5fpggEib5QicCewZGj0ZRWDVBduQ/640?wx_fmt=png" alt=""></p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHhCiab6dlIM2ibMY4KPSrup1iaHNp6ciayzJ0oBRcvyIibRLltBDSw1zyxMA/640?wx_fmt=png" alt=""></p>
<p><strong>Linux内核学习资源</strong></p>
<p><strong>源码:</strong></p>
<p><a href="https://elixir.bootlin.com/linux/latest/source">https://elixir.bootlin.com/linux/latest/source</a></p>
<p>在线交叉索引看源码,包括Linux几乎所有版本;</p>
<p><a href="https://github.com/torvalds/linux">https://github.com/torvalds/linux</a></p>
<p>内核github仓库,可以下载本地,编译,修改和开发。</p>
<p><strong>网站</strong></p>
<p><strong><a href="http://www.kernel.org">http://www.kernel.org</a></strong></p>
<p>可以通过这个网站上下载内核的源代码和补丁、跟踪内核bug等</p>
<p><strong><a href="http://lwn.net">http://lwn.net</a></strong></p>
<p>Linux 内核最新消息,提供给了定期的与内核开发相关的报道</p>
<p><strong><a href="https://www.wiki.kernel.org/">https://www.wiki.kernel.org/</a></strong></p>
<p>各种子模块wiki列表</p>
<p><strong><a href="http://www.linuxdoc.org">http://www.linuxdoc.org</a></strong></p>
<p>Linux Documentation Project(Linux文档项目),拥有大量称为“HowTo”<br>
的文档,其中一些是技术性的,并涉及到一些内核相关的主题。</p>
<p><strong><a href="http://www.kerneltravel.net/">http://www.kerneltravel.net/</a></strong></p>
<p>国内Linux内核之旅开源社区</p>
<p><strong><a href="http://www.linux-mm.org">http://www.linux-mm.org</a></strong><br>
该页面面向Linux内存管理开发,其中包含大量有用的信息,并且还包含大量与内核相关的Web站点链接。</p>
<p><strong><a href="http://www.wowotech.net">http://www.wowotech.net</a></strong></p>
<p>博客专注分享linux内核知识(偏嵌入式方向), 很多文章都非常精华和透彻,值得内核学习者学习;</p>
<p><strong><a href="https://blog.csdn.net/gatieme">https://blog.csdn.net/gatieme</a></strong></p>
<p>操作系统优质博客,可以学习linux 调度相关内核知识;</p>
<p><strong><a href="https://blog.csdn.net/dog250">https://blog.csdn.net/dog250</a></strong></p>
<p>dog250的文章都比较深刻,属于Linux内核进阶,可能不太适合入门,建议入门后,再看这里文章,会让你醍醐灌顶。</p>
<p><strong><a href="https://www.kernel.org/doc">https://www.kernel.org/doc</a></strong></p>
<p>内核文档</p>
<p><strong>书籍</strong></p>
<p>《深入理解Linux内核》</p>
<p>《深入Linux内核架构》</p>
<p>《Linux内核设计与实现》</p>
<p>《Linux内核源代码情景分析》</p>
<p>《深入理解LINUX网络内幕》</p>
<p>《深入理解Linux虚拟内存管理》</p>
<p>《Linux设备驱动程序》</p>
<h3 id="git分布式版本控制系统">Git分布式版本控制系统<a class="td-heading-self-link" href="#git%e5%88%86%e5%b8%83%e5%bc%8f%e7%89%88%e6%9c%ac%e6%8e%a7%e5%88%b6%e7%b3%bb%e7%bb%9f" aria-label="Heading self-link"></a></h3>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHplRZhR4pd07qoWDXz6icgRfqLyyC9SbZnxx2PIVCOyYHuWQjYDC3IDw/640?wx_fmt=png" alt=""></p>
<p>2005 年,Linus还创建了 <strong>Git</strong>,这是非常流行的分布式源代码控制系统。迅速将 Linux 内核源代码树从专有 Bitkeeper 迁移到新创建的开源 Git。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHK6ewlMwmzxJxkuJ2Wb4dUxdUmOuUcVybz2NpicpKYHpbW3Kf8Rg9iabA/640?wx_fmt=png" alt=""></p>
<p><strong>git 架构</strong></p>
<p><strong>Git</strong> 是出于需要而创建的,不是因为发现源代码控制很有趣,而是因为其他多数源代码控制系统不好用,不能满足当时开发需求,并且 git 在 Linux 开发模型中确实运行得相当好,BitKeeper变得站不住脚。</p>
<p>完美适应现代开源软件的开发模式,分布式版本管理:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHq1UTS3ZCgoIbcKBtU67SJcsLUS7osnicsG9LzkJM75hkjT1O9wVat9g/640?wx_fmt=png" alt=""></p>
<h3 id="linux内核名人堂">Linux内核名人堂<a class="td-heading-self-link" href="#linux%e5%86%85%e6%a0%b8%e5%90%8d%e4%ba%ba%e5%a0%82" aria-label="Heading self-link"></a></h3>
<p>让我们膜拜一下对Linux内核做出核心贡献的大神们:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHtEVHKmWufP3VMY68yCQnh3fgdg40AFwfHhLmz2PaqLqXZJHCKQSn9Q/640?wx_fmt=jpeg" alt=""></p>
<p><strong>林纳斯·班奈狄克·托瓦兹</strong>(1969年12月28日-),生于芬兰赫尔辛基市,拥有美国国籍,Linux内核的最早作者,随后发起了这个开源项目,担任Linux内核的首要架构师与项目协调者,是当今世界最著名的电脑程序员、黑客之一。他还发起了开源项目Git,并为主要的开发者。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHxticTE1khTNhUIXzRscgET2fazkOk0ISAzIrORD6X51wTUPSlfzCUxg/640?wx_fmt=jpeg" alt=""></p>
<p><strong>大卫·史提芬·米勒</strong>(英语:David Stephen Miller,1974年11月26日-),网络昵称为 DaveM,生于美国新泽西州新布朗斯维克,著名程式员与骇客,负责Linux核心网络功能以及SPARC平台的实作。他也参与其他开源软件的开发,是GCC督导委员会的成员之一。根据2013年8月的统计,米勒是Linux核心源代码第二大的贡献者,自2005年开始,已经提交过4989个patch。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHC4qCA4jxF7rIrqwJpzFhzESIQx7ibZ5dIXkicz0XsBcwAibveORZ4NA5g/640?wx_fmt=jpeg" alt=""></p>
<p><strong>葛雷格·克罗-哈曼</strong>(英语:Greg Kroah-Hartman,姓名缩写为GKH), Linux核心开发者,目前为 Linux 核心中稳定分支(<code>-stable</code>)的维护者[2],他也是staging 子系统[2]、USB[2]driver core、debugfs、kref、kobject、sysfs kernel 子系统[2]、 TTY layer [2]、linux-hotplug、Userspace I/O(与 Hans J. Koch 共同维护)等专案的维护者[2],也创立了udev专案。除此之外,他亦协助维护Gentoo Linux中上述程式及 kernel 的套件。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHiaR4nLoIu5MJ2cAFdjykndwROoicmZXR7rpqqXyReD4RCiaPwOrsc2urQ/640?wx_fmt=jpeg" alt=""></p>
<p><strong>艾伦·考克斯</strong>(英语:Alan Cox,1968年7月22日-)是一名英国程序员,生于英格兰索利赫尔。他自1991年开始投入Linux内核的开发工作,在开发者社群中有很高的地位,是Linux开发工作中的关键人物之一。他负责维护Linux内核 2.2版这个分支,在2.4版中也提供许多程式码,拥有自己的分支版本。他住在威尔斯斯旺西,他的妻子于2015年逝世[1][2][3]。2020年他再婚[4][5]。他于1991年在斯旺西大学获得计算机科学理学学士学位,2005年在那里获得工商管理硕士学位[6]。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHuOQOEoMQhVfJibrcPjBxIy3Y61qGos8lFbz70cSf8Tycwy8OQsX5Nhg/640?wx_fmt=jpeg" alt=""></p>
<p><strong>英格·蒙内</strong>(匈牙利语:Ingo Molnár),匈牙利软件程序员与骇客,在linux内核上有许多贡献,也拥有自己的linux分支版本。对于操作系统的安全性与效能提升方面,他的声名卓著,在linux内核中,他于Linux-2.6.0版加入O(1)排程器,在 Linux-2.6.23版中加入<strong>完全公平调度器CFS</strong>(Completely Fair Scheduler)。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHBWhcnGvYiaDtbmsqnbjoRFnyXSTs5ibMAZ7Tm0vJuU573vKyrpzvvIjw/640?wx_fmt=png" alt=""></p>
<p><strong>米格尔·德伊卡萨</strong>(西班牙语:Miguel de Icaza ,1972年11月23日-),生于墨西哥市,著名墨西哥籍自由软件开发者,为GNOME项目与Mono项目的发起人。但后来[何时?]退出了GNOME项目。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHdkfW1X7mmFsq1ShM2iaHl6XJD5RPK1xaOJ60hAxiczdEDzGWrw2HorhA/640?wx_fmt=jpeg" alt=""></p>
<p><strong>罗伯特·马修·拉姆</strong>(英语:Robert Matthew Love,1981年9月25日-),生于美国佛罗里达州,为著名自由软件程式开发者、作家,现职为google软件工程师。现居于波士顿。他是linux核心的主要开发者之一,主要负责程式排程、先占式核心、虚拟内存子系统、核心事件层。他也加入了GNOME计划。目前他在google,主要负责Android系统的开发。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHn9h6QWB8CfkNDIeoLLNgW4p3VkHveT61gVHiccEtjfkF708FfXUW3Aw/640?wx_fmt=jpeg" alt=""></p>
<p><strong>安德鲁·基斯·保罗·莫顿</strong>(英语:Andrew Keith Paul Morton,1959年-),生于英国英格兰,澳洲软件工程师与著名骇客。他是Linux核心开发社群的领导者之一,现为ext3的共同维护者,负责区块装置的日志层(Journaling layer for block devices,JBD)。他也是mm tree的负责人。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgH2O1Ze7oATYXWrxfiaHkZqB5ggRna9RRft2huTliaQWyNrV061Z0q3icKQ/640?wx_fmt=jpeg" alt=""></p>
<p><strong>埃里克·斯蒂芬·雷蒙</strong>(英语:Eric Steven Raymond,1957年12月4日-),是一名程序员,《大教堂与市集》的作者、《新黑客词典》(&ldquo;Jargon File&rdquo;)的维护人、著名黑客。作为《新黑客词典》的主要编撰人以及维护者,雷蒙很早就被认为是黑客文化的历史学家以及人类学家。但是在1997年以后,雷蒙被广泛公认为是开放源代码运动的主要领导者之一,并且是最为大众所知道(并最具争议性)的黑客。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHutx5g8HmjnzMFaeXZ7kc8TOttGwG1GUg5FswuJtelvTo0GWbZTOjPQ/640?wx_fmt=jpeg" alt=""></p>
<p><strong>西奥多·曹</strong>(英语:Theodore Y. Ts&rsquo;o,1968年1月23日-),小名<strong>泰德·曹</strong>(Ted Tso),汉名<strong>曹子德</strong>[1],生于美国加利福尼亚州帕罗奥图,著名的自由软件工程师,专长于文件系统设计。他是Linux内核在北美最早的开发者,负责ext2、ext3与ext4文件系统的开发与维护工作。他也是e2fsprogs的开发者。为自由标准组织的创始者之一,也曾担任Linux基金会首席技术官。</p>
<p>由于互联网发达,当前不管是从个人爱好,还是工作原因,对内核贡献的国人越来越多:</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_png/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHQ2BEn5hagVPEelr1qE2EHlGWclItckm0saPvuB5zACbZWzIB6kbNeQ/640?wx_fmt=png" alt=""></p>
<p><a href="http://www.remword.com/kps">http://www.remword.com/kps</a>_result/all_whole_line_country.html</p>
<h3 id="最后">最后<a class="td-heading-self-link" href="#%e6%9c%80%e5%90%8e" aria-label="Heading self-link"></a></h3>
<p>30年的时间,Linux从一个个人玩具变成现在庞然大物,估值超过100亿美元,Linux还带来一股开源潮流,让开源软件百花齐放,对计算机发展和开源文化起到极大促进作用。</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHibFAnWeJmNESo5lWBCcEYdI2MpVkiabQ6n4B3FRpv1FwfDpYaTDWXG4w/640?wx_fmt=jpeg" alt=""></p>
<p>Linux 庞大的生态与发展过程,Linus伟大而富有创造力并不足以在一篇文章中尽述。</p>
<p>匆匆30 年,Linux 已经不仅仅是改变了世界,而且已经成为了这个世界不可或缺的一部分感谢 Linus Torvalds,感谢为之致力的一切贡献者!</p>
<p><img src="https://mmbiz.qpic.cn/mmbiz_jpg/cYSwmJQric6nhH4RQfgaJfjrfmLsALibgHxfSUWjnl9ia6I5GPetib9tqehO96tNWdaPEQzicHIjk2QQ1eNq5WyQTDw/640?wx_fmt=jpeg" alt=""></p>
<p>最后,为了致敬Linux,希望大家三连支持,让更多人可以看到!</p>
<h3 id="参考和扩展">参考和扩展<a class="td-heading-self-link" href="#%e5%8f%82%e8%80%83%e5%92%8c%e6%89%a9%e5%b1%95" aria-label="Heading self-link"></a></h3>
<p><a href="http://www.atguigu.com/jsfx/5694.html">http://www.atguigu.com/jsfx/5694.html</a></p>
<p><a href="https://opensource.com/article/16/12/yearbook-9-lessons-25-years-linux-kernel-development">https://opensource.com/article/16/12/yearbook-9-lessons-25-years-linux-kernel-development</a></p>
<p><a href="https://www.reddit.com/r/linux/comments/2pqqla/kernel">https://www.reddit.com/r/linux/comments/2pqqla/kernel</a>_commit_4_year_old_girl_fixes_formatting_to/utm_source=amp&amp;utm_medium=&amp;utm_content=post_title</p>
<p><a href="http://oss.org.cn/ossdocs/linux/kernel/a1/index.html">http://oss.org.cn/ossdocs/linux/kernel/a1/index.html</a></p>
<p><a href="http://www.wowotech.net/linux_kenrel/11.html">http://www.wowotech.net/linux_kenrel/11.html</a></p>
<p><a href="https://www.wikiwand.com/zh/Linux">https://www.wikiwand.com/zh/Linux</a></p>
<p><a href="https://zh.wikipedia.org/wiki/Category:Linux%E6%A0%B8%E5%BF%83%E9%A7%AD%E5%AE%A2">https://zh.wikipedia.org/wiki/Category:Linux%E6%A0%B8%E5%BF%83%E9%A7%AD%E5%AE%A2</a></p>
<p><a href="http://www.chromium.org/chromium-os/chromiumos-design-docs/software-architecture">http://www.chromium.org/chromium-os/chromiumos-design-docs/software-architecture</a></p>
<p>- END -</p></description></item><item><title>Blog: ext 文件系统机制原理剖析</title><link>https://desistdaydream.github.io/blog/copy/ext_filesystem/</link><pubDate>Sun, 25 Oct 2020 00:00:00 +0000</pubDate><guid>https://desistdaydream.github.io/blog/copy/ext_filesystem/</guid><description>
<p>原文链接:<a href="https://www.junmajinlong.com/linux/ext_filesystem/">https://www.junmajinlong.com/linux/ext_filesystem/</a></p>
<hr>
<p><strong><a href="https://www.junmajinlong.com/linux/index">回到 Linux 基础系列文章大纲</a></strong><br>
<strong><a href="https://www.junmajinlong.com/shell/index">回到 Shell 系列文章大纲</a></strong></p>
<hr>
<p>将磁盘进行分区,分区是将磁盘按柱面进行物理上的划分。划分好分区后还要进行格式化,然后再挂载才能使用 (不考虑其他方法)。格式化分区的过程其实就是创建文件系统。</p>
<p>文件系统的类型有很多种,如 CentOS 5 和 CentOS 6 上默认使用的 ext2/ext3/ext4,CentOS 7 上默认使用的 xfs,windows 上的 NTFS,光盘类的文件系统 ISO9660,MAC 上的混合文件系统 HFS,网络文件系统 NFS,Oracle 研发的 btrfs,还有老式的 FAT/FAT32 等。</p>
<p>本文将非常全面且详细地介绍 ext 家族的文件系统,中间还非常详细地介绍了 inode、软链接、硬链接、数据存储方式以及操作文件的理论,基本上看完本文,对文件系统的宏观理解将再无疑惑。ext 家族的文件系统有 ext2/ext3/ext4,ext3 是有日志的 ext2 改进版,ext4 对相比 ext3 做了非常多的改进。虽然 xfs/btrfs 等文件系统有所不同,但它们只是在实现方式上不太同,再加上属于自己的特性而已。</p>
<h2 id="block的出现-block的出现block-的出现"><a href="#block%E7%9A%84%E5%87%BA%E7%8E%B0" title="block的出现"></a>block 的出现<a class="td-heading-self-link" href="#block%e7%9a%84%e5%87%ba%e7%8e%b0-block%e7%9a%84%e5%87%ba%e7%8e%b0block-%e7%9a%84%e5%87%ba%e7%8e%b0" aria-label="Heading self-link"></a></h2>
<p>硬盘最底层的读写 IO 一次是一个扇区 512 字节,如果要读写大量文件,以扇区为单位肯定很慢很消耗性能,所以硬盘使用了一个称作逻辑块的概念。逻辑块是逻辑的,由磁盘驱动器负责维护和操作,它并非是像扇区一样物理划分的。一个逻辑块的大小可能包含一个或多个扇区,每个逻辑块都有唯一的地址,称为 LBA。有了逻辑块之后,磁盘控制器对数据的操作就以逻辑块为单位,一次读写一个逻辑块,磁盘控制器知道如何将逻辑块翻译成对应的扇区并读写数据。</p>
<p>到了 Linux 操作系统层次,通过文件系统提供了一个也称为块的读写单元,文件系统数据块的大小一般为 1024bytes (1K) 或 2048bytes (2K) 或 4096bytes (4K)。文件系统数据块也是逻辑概念,是文件系统层次维护的,而磁盘上的逻辑数据块是由磁盘控制器维护的,文件系统的 IO 管理器知道如何将它的数据块翻译成磁盘维护的数据块地址 LBA。对于使用文件系统的 IO 操作来说,比如读写文件,这些 IO 的基本单元是文件系统上的数据块,一次读写一个文件系统数据块。比如需要读一个或多个块时,文件系统的 IO 管理器首先计算这些文件系统块对应在哪些磁盘数据块,也就是计算出 LBA,然后通知磁盘控制器要读取哪些块的数据,硬盘控制器将这些块翻译成扇区地址,然后从扇区中读取数据,再通过硬盘控制器将这些扇区数据重组写入到内存中去。</p>
<p>本文既然是讨论文件系统的,那么重点自然是在文件系统上而不是在磁盘上,所以后文出现的 block 均表示的是文件系统的数据块而不是磁盘维护的逻辑块。</p>
<p>文件系统 block 的出现使得在文件系统层面上读写性能大大提高,也大量减少了碎片。但是它的副作用是可能造成空间浪费。由于文件系统以 block 为读写单元,即使存储的文件只有 1K 大小也将占用一个 block,剩余的空间完全是浪费的。在某些业务需求下可能大量存储小文件,这会浪费大量的空间。</p>
<p>尽管有缺点,但是其优点足够明显,在当下硬盘容量廉价且追求性能的时代,使用 block 是一定的。</p>
<h2 id="inode的出现-inode的出现inode-的出现"><a href="#inode%E7%9A%84%E5%87%BA%E7%8E%B0" title="inode的出现"></a>inode 的出现<a class="td-heading-self-link" href="#inode%e7%9a%84%e5%87%ba%e7%8e%b0-inode%e7%9a%84%e5%87%ba%e7%8e%b0inode-%e7%9a%84%e5%87%ba%e7%8e%b0" aria-label="Heading self-link"></a></h2>
<p>如果存储的 1 个文件占用了大量的 block 读取时会如何?假如 block 大小为 1KB,仅仅存储一个 10M 的文件就需要 10240 个 block,而且这些 blocks 很可能在位置上是不连续在一起的 (不相邻),读取该文件时难道要从前向后扫描整个文件系统的块,然后找出属于该文件的块吗?显然是不应该这么做的,因为太慢太傻瓜式了。再考虑一下,读取一个只占用 1 个 block 的文件,难道只读取一个 block 就结束了吗?并不是,仍然是扫描整个文件系统的所有 block,因为它不知道什么时候扫描到,扫描到了它也不知道这个文件是不是已经完整而不需要再扫描其他的 block。</p>
<p>另外,每个文件都有属性 (如权限、大小、时间戳等),这些属性类的元数据存储在哪里呢?难道也和文件的数据部分存储在块中吗?如果一个文件占用多个 block 那是不是每个属于该文件的 block 都要存储一份文件元数据?但是如果不在每个 block 中存储元数据文件系统又怎么知道某一个 block 是不是属于该文件呢?但是显然,每个数据 block 中都存储一份元数据太浪费空间。</p>
<p>文件系统设计者当然知道这样的存储方式很不理想,所以需要优化存储方式。如何优化?对于这种类似的问题的解决方法是使用索引,通过扫描索引找到对应的数据,而且索引可以存储部分数据。</p>
<p>在文件系统上索引技术具体化为索引节点 (index node),在索引节点上存储的部分数据即为文件的属性元数据及其他少量信息。一般来说索引占用的空间相比其索引的文件数据而言占用的空间就小得多,扫描它比扫描整个数据要快得多,否则索引就没有存在的意义。这样一来就解决了前面所有的问题。</p>
<p>在文件系统上的术语中,索引节点称为 inode。在 inode 中存储了 inode 号(注,inode 中并未存储 inode num,但为了方便理解,这里暂时认为它存储了 inode 号)、文件类型、权限、文件所有者、大小、时间戳等元数据信息,最重要的是还存储了指向属于该文件 block 的指针,这样读取 inode 就可以找到属于该文件的 block,进而读取这些 block 并获得该文件的数据。由于后面还会介绍一种指针,为了方便称呼和区分,暂且将这个 inode 记录中指向文件 data block 的指针称之为 block 指针。以下是 ext2 文件系统中 inode 包含的信息示例:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>Inode: 12 Type: regular Mode: 0644 Flags: 0x0
</span></span><span style="display:flex;"><span>Generation: 1454951771 Version: 0x00000000:00000001
</span></span><span style="display:flex;"><span>User: 0 Group: 0 Size: 5
</span></span><span style="display:flex;"><span>File ACL: 0 Directory ACL: 0
</span></span><span style="display:flex;"><span>Links: 1 Blockcount: 8
</span></span><span style="display:flex;"><span>Fragment: Address: 0 Number: 0 Size: 0
</span></span><span style="display:flex;"><span> ctime: 0x5b628db2:15e0aff4 -- Thu Aug 2 12:50:58 2018
</span></span><span style="display:flex;"><span> atime: 0x5b628db2:15e0aff4 -- Thu Aug 2 12:50:58 2018
</span></span><span style="display:flex;"><span> mtime: 0x5b628db2:15e0aff4 -- Thu Aug 2 12:50:58 2018
</span></span><span style="display:flex;"><span>crtime: 0x5b628db2:15e0aff4 -- Thu Aug 2 12:50:58 2018
</span></span><span style="display:flex;"><span>Size of extra inode fields: 28
</span></span><span style="display:flex;"><span>BLOCKS:
</span></span><span style="display:flex;"><span>(0):1024
</span></span><span style="display:flex;"><span>TOTAL: 1
</span></span></code></pre></div><p>一般 inode 大小为 128 字节或 256 字节,相比那些 MB 或 GB 计算的文件数据而言小得多的多,但也要知道可能一个文件大小小于 inode 大小,例如只占用 1 个字节的文件。</p>
<h2 id="bmap出现-bmap出现bmap-出现"><a href="#bmap%E5%87%BA%E7%8E%B0" title="bmap出现"></a>bmap 出现<a class="td-heading-self-link" href="#bmap%e5%87%ba%e7%8e%b0-bmap%e5%87%ba%e7%8e%b0bmap-%e5%87%ba%e7%8e%b0" aria-label="Heading self-link"></a></h2>
<p>在向硬盘存储数据时,文件系统需要知道哪些块是空闲的,哪些块是已经占用了的。最笨的方法当然是从前向后扫描,遇到空闲块就存储一部分,继续扫描直到存储完所有数据。</p>
<p>优化的方法当然也可以考虑使用索引,但是仅仅 1G 的文件系统就有 1KB 的 block 共 1024*1024=1048576 个,这仅仅只是 1G,如果是 100G、500G 甚至更大呢,仅仅使用索引索引的数量和空间占用也将极大,这时就出现更高一级的优化方法:使用块位图 (bitmap 简称 bmap)。</p>
<p>位图只使用 0 和 1 标识对应 block 是空闲还是被占用,0 和 1 在位图中的位置和 block 的位置一一对应,第一位标识第一个块,第二个位标识第二个块,依次下去直到标记完所有的 block。</p>
<p>考虑下为什么块位图更优化。在位图中 1 个字节 8 个位,可以标识 8 个 block。对于一个 block 大小为 1KB、容量为 1G 的文件系统而言,block 数量有 1024*1024 个,所以在位图中使用 1024*1024 个位共 1024*1024/8=131072 字节 = 128K,即 1G 的文件只需要 128 个 block 做位图就能完成一一对应。通过扫描这 100 多个 block 就能知道哪些 block 是空闲的,速度提高了非常多。</p>
<p>但是要注意,<strong>bmap 的优化针对的是写优化,因为只有写才需要找到空闲 block 并分配空闲 block</strong>。对于读而言,只要通过 inode 找到了 block 的位置,cpu 就能迅速计算出 block 在物理磁盘上的地址,cpu 的计算速度是极快的,计算 block 地址的时间几乎可以忽略,那么读速度基本认为是受硬盘本身性能的影响而与文件系统无关。大多数稍大一点的文件可能都会存储在不连续的 block 上,而且使用了一段时间的文件系统可能会有不少碎片,这时硬盘的随机读取性能直接决定读数据的速度,这也是机械硬盘速度相比固态硬盘慢的多的多的原因之一,而且固态硬盘的随机读和连续读取速度几乎是一致的,对它来说,文件系统碎片的多少并不会影响读取速度。</p>
<p>虽然 bmap 已经极大的优化了扫描,但是仍有其瓶颈:如果文件系统是 100G 呢?100G 的文件系统要使用 128*100=12800 个 1KB 大小的 block,这就占用了 12.5M 的空间了。试想完全扫描 12800 个很可能不连续的 block 这也是需要占用一些时间的,虽然快但是扛不住每次存储文件都要扫描带来的巨大开销。</p>
<p>所以需要再次优化,如何优化?简而言之就是将文件系统划分开形成块组,至于块组的介绍放在后文。</p>
<h2 id="inode表的出现-inode表的出现inode-表的出现"><a href="#inode%E8%A1%A8%E7%9A%84%E5%87%BA%E7%8E%B0" title="inode表的出现"></a>inode 表的出现<a class="td-heading-self-link" href="#inode%e8%a1%a8%e7%9a%84%e5%87%ba%e7%8e%b0-inode%e8%a1%a8%e7%9a%84%e5%87%ba%e7%8e%b0inode-%e8%a1%a8%e7%9a%84%e5%87%ba%e7%8e%b0" aria-label="Heading self-link"></a></h2>
<p>回顾下 inode 相关信息:inode 存储了 inode 号(注,同前文,inode 中并未存储 inode num)、文件属性元数据、指向文件占用的 block 的指针;每一个 inode 占用 128 字节或 256 字节。</p>
<p>现在又出现问题了,一个文件系统中可以说有无数多个文件,每一个文件都对应一个 inode,难道每一个仅 128 字节的 inode 都要单独占用一个 block 进行存储吗?这太浪费空间了。</p>
<p>所以更优的方法是将多个 inode 合并存储在 block 中,对于 128 字节的 inode,一个 block 存储 8 个 inode,对于 256 字节的 inode,一个 block 存储 4 个 inode。这就使得每个存储 inode 的块都不浪费。</p>
<p>在 ext 文件系统上,将这些物理上存储 inode 的 block 组合起来,在逻辑上形成一张 inode 表 (inode table) 来记录所有的 inode。</p>
<p>举个例子,每一个家庭都要向派出所登记户口信息,通过户口本可以知道家庭住址,而每个镇或街道的派出所将本镇或本街道的所有户口整合在一起,要查找某一户地址时,在派出所就能快速查找到。inode table 就是这里的派出所。它的内容如下图所示。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20180830092223810-1825870107.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20180830092223810-1825870107.jpg" alt="">
</a></p>
<p>再细细一思考,就能发现一个大的文件系统仍将占用大量的块来存储 inode,想要找到其中的一个 inode 记录也需要不小的开销,尽管它们已经形成了一张逻辑上的表,但扛不住表太大记录太多。那么如何快速找到 inode,这同样是需要优化的,优化的方法是将文件系统的 block 进行分组划分,每个组中都存有本组 inode table 范围、bmap 等。</p>
<h2 id="imap的出现-imap的出现imap-的出现"><a href="#imap%E7%9A%84%E5%87%BA%E7%8E%B0" title="imap的出现"></a>imap 的出现<a class="td-heading-self-link" href="#imap%e7%9a%84%e5%87%ba%e7%8e%b0-imap%e7%9a%84%e5%87%ba%e7%8e%b0imap-%e7%9a%84%e5%87%ba%e7%8e%b0" aria-label="Heading self-link"></a></h2>
<p>前面说 bmap 是块位图,用于标识文件系统中哪些 block 是空闲哪些 block 是占用的。</p>
<p>对于 inode 也一样,在存储文件 (Linux 中一切皆文件) 时需要为其分配一个 inode 号。但是在格式化创建文件系统后所有的 inode 号都已被事先计算好(创建文件系统时会为每个块组计算好该块组拥有哪些 inode 号),因此产生了问题:要为文件分配哪一个 inode 号呢?又如何知道某一个 inode 号是否已经被分配了呢?</p>
<p>既然是” 是否被占用” 的问题,使用位图是最佳方案,像 bmap 记录 block 的占用情况一样。标识 inode 号是否被分配的位图称为 inodemap 简称为 imap。这时要为一个文件分配 inode 号只需扫描 imap 即可知道哪一个 inode 号是空闲的。</p>
<p>imap 存在着和 bmap 和 inode table 一样需要解决的问题:如果文件系统比较大,imap 本身就会很大,每次存储文件都要进行扫描,会导致效率不够高。同样,优化的方式是将文件系统占用的 block 划分成块组,每个块组有自己的 imap 范围。</p>
<h2 id="块组的出现-块组的出现块组的出现"><a href="#%E5%9D%97%E7%BB%84%E7%9A%84%E5%87%BA%E7%8E%B0" title="块组的出现"></a>块组的出现<a class="td-heading-self-link" href="#%e5%9d%97%e7%bb%84%e7%9a%84%e5%87%ba%e7%8e%b0-%e5%9d%97%e7%bb%84%e7%9a%84%e5%87%ba%e7%8e%b0%e5%9d%97%e7%bb%84%e7%9a%84%e5%87%ba%e7%8e%b0" aria-label="Heading self-link"></a></h2>
<p>前面一直提到的优化方法是将文件系统占用的 block 划分成块组 (block group),解决 bmap、inode table 和 imap 太大的问题。</p>
<p>在物理层面上的划分是将磁盘按柱面划分为多个分区,即多个文件系统;在逻辑层面上的划分是将文件系统划分成块组。每个文件系统包含多个块组,每个块组包含多个元数据区和数据区:元数据区就是存储 bmap、inode table、imap 等的数据;数据区就是存储文件数据的区域。注意块组是逻辑层面的概念,所以并不会真的在磁盘上按柱面、按扇区、按磁道等概念进行划分。</p>
<h2 id="块组的划分-块组的划分块组的划分"><a href="#%E5%9D%97%E7%BB%84%E7%9A%84%E5%88%92%E5%88%86" title="块组的划分"></a>块组的划分<a class="td-heading-self-link" href="#%e5%9d%97%e7%bb%84%e7%9a%84%e5%88%92%e5%88%86-%e5%9d%97%e7%bb%84%e7%9a%84%e5%88%92%e5%88%86%e5%9d%97%e7%bb%84%e7%9a%84%e5%88%92%e5%88%86" aria-label="Heading self-link"></a></h2>
<p>块组在文件系统创建完成后就已经划分完成了,也就是说元数据区 bmap、inode table 和 imap 等信息占用的 block 以及数据区占用的 block 都已经划分好了。那么文件系统如何知道一个块组元数据区包含多少个 block,数据区又包含多少 block 呢?</p>
<p><strong>它只需确定一个数据 —— 每个 block 的大小,再根据 bmap 至多只能占用一个完整的 block 的标准就能计算出块组如何划分</strong>。如果文件系统非常小,所有的 bmap 总共都不能占用完一个 block,那么也只能空闲 bmap 的 block 了。</p>
<p>每个 block 的大小在创建文件系统时可以人为指定,不指定也有默认值。</p>
<p>假如现在 block 的大小是 1KB,一个 bmap 完整占用一个 block 能标识 1024*8=8192 个 block (当然这 8192 个 block 是数据区和元数据区共 8192 个,因为元数据区分配的 block 也需要通过 bmap 来标识)。每个 block 是 1K,每个块组是 8192K 即 8M,创建 1G 的文件系统需要划分 1024/8=128 个块组,如果是 1.1G 的文件系统呢?128+12.8=128+13=141 个块组。</p>
<p>每个组的 block 数目是划分好了,但是每个组设定多少个 inode 号呢?inode table 占用多少 block 呢?这需要由系统决定了,因为描述” 每多少个数据区的 block 就为其分配一个 inode 号” 的指标默认是我们不知道的,当然创建文件系统时也可以人为指定这个指标或者百分比例。见后文”<a href="#deep_into_inode">inode 深入</a> “。</p>
<p>使用 dumpe2fs 可以将 ext 类的文件系统信息全部显示出来,当然 bmap 是每个块组固定一个 block 的不用显示,imap 比 bmap 更小所以也只占用 1 个 block 不用显示。</p>
<p>下图是一个文件系统的部分信息,在这些信息的后面还有每个块组的信息,其实这里面的很多信息都可以通过几个比较基本的元数据推导出来。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615093736759-1554527092.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615093736759-1554527092.jpg" alt="">
</a></p>
<p>从这张表中能计算出文件系统的大小,该文件系统共 4667136 个 blocks,每个 block 大小为 4K,所以文件系统大小为 <code>4667136*4/1024/1024=17.8GB</code>。</p>
<p>也能计算出分了多少个块组,因为每一个块组的 block 数量为 32768,所以块组的数量为 4667136/32768=142.4 即 143 个块组。由于块组从 0 开始编号,所以最后一个块组编号为 Group 142。如下图所示是最后一个块组的信息。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615093808509-902863729.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615093808509-902863729.jpg" alt="">
</a></p>
<p>将上文描述的 bmap、inode table、imap、数据区的 blocks 和块组的概念组合起来就形成了一个文件系统,当然这还不是完整的文件系统。完整的文件系统如下图</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20180727160411876-443793371.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20180727160411876-443793371.jpg" alt="">
</a></p>
<p>首先,该图中多了 Boot Block、Super Block、GDT、Reserver GDT 这几个概念。下面会分别介绍它们。</p>
<p>然后,图中指明了块组中每个部分占用的 block 数量,除了 superblock、bmap、imap 能确定占用 1 个 block,其他的部分都不能确定占用几个 block。</p>
<p>最后,图中指明了 Superblock、GDT 和 Reserved GDT 是同时出现且不一定存在于每一个块组中的,也指明了 bmap、imap、inode table 和 data blocks 是每个块组都有的。</p>
<h2 id="引导块-引导块引导块"><a href="#%E5%BC%95%E5%AF%BC%E5%9D%97" title="引导块"></a>引导块<a class="td-heading-self-link" href="#%e5%bc%95%e5%af%bc%e5%9d%97-%e5%bc%95%e5%af%bc%e5%9d%97%e5%bc%95%e5%af%bc%e5%9d%97" aria-label="Heading self-link"></a></h2>
<p>即上图中的 Boot Block 部分,也称为 boot sector。它位于分区上的第一个块,占用 1024 字节,并非所有分区都有这个 boot sector,只有装了操作系统的主分区和装了操作系统的逻辑分区才有。里面存放的也是 boot loader,这段 boot loader 称为 VBR (主分区装操作系统时) 或 EBR (扩展分区装操作系统时),这里的 Boot loader 和 mbr 上的 boot loader 是存在交错关系的。开机启动的时候,首先加载 mbr 中的 bootloader,然后定位到操作系统所在分区的 boot serctor 上加载此处的 boot loader。如果是多系统,加载 mbr 中的 bootloader 后会列出操作系统菜单,菜单上的各操作系统指向它们所在分区的 boot sector 上。它们之间的关系如下图所示。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170627160437071-1671926976.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170627160437071-1671926976.jpg" alt="">
</a></p>
<p>但是,这种方式的操作系统菜单早已经弃之不用了,而是使用 grub 来管理启动菜单。尽管如此,在安装操作系统时,仍然有一步是选择 boot loader 安装位置的步骤。</p>
<h2 id="超级块-superblock-超级块superblock超级块-superblock"><a href="#%E8%B6%85%E7%BA%A7%E5%9D%97-superblock" title="超级块(superblock)"></a>超级块 (superblock)<a class="td-heading-self-link" href="#%e8%b6%85%e7%ba%a7%e5%9d%97-superblock-%e8%b6%85%e7%ba%a7%e5%9d%97superblock%e8%b6%85%e7%ba%a7%e5%9d%97-superblock" aria-label="Heading self-link"></a></h2>
<p>既然一个文件系统会分多个块组,那么文件系统怎么知道分了多少个块组呢?每个块组又有多少 block 多少 inode 号等等信息呢?还有,文件系统本身的属性信息如各种时间戳、block 总数量和空闲数量、inode 总数量和空闲数量、当前文件系统是否正常、什么时候需要自检等等,它们又存储在哪里呢?</p>
<p>毫无疑问,这些信息必须要存储在 block 中。存储这些信息占用 1024 字节,所以也要一个 block,这个 block 称为超级块 (superblock),它的 block 号可能为 0 也可能为 1。<strong>如果 block 大小为 1K,则引导块正好占用一个 block,这个 block 号为 0,所以 superblock 的号为 1;如果 block 大小大于 1K,则引导块和超级块同置在一个 block 中,这个 block 号为 0。总之 superblock 的起止位置是第二个 1024 (1024-2047) 字节</strong>。</p>
<p>使用 df 命令读取的就是每个文件系统的 superblock,所以它的统计速度非常快。相反,用 du 命令查看一个较大目录的已用空间就非常慢,因为不可避免地要遍历整个目录的所有文件。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@xuexi ~]# df -hT
</span></span><span style="display:flex;"><span>Filesystem Type Size Used Avail Use% Mounted on
</span></span><span style="display:flex;"><span>/dev/sda3 ext4 18G 1.7G 15G 11% /
</span></span><span style="display:flex;"><span>tmpfs tmpfs 491M 0 491M 0% /dev/shm
</span></span><span style="display:flex;"><span>/dev/sda1 ext4 190M 32M 149M 18% /boot
</span></span></code></pre></div><p>superblock 对于文件系统而言是至关重要的,超级块丢失或损坏必将导致文件系统的损坏。所以旧式的文件系统将超级块备份到每一个块组中,但是这又有所空间浪费,所以 ext2 文件系统只在块组 0、1 和 3、5、7 幂次方的块组中保存超级块的信息,如 Group9、Group25 等。尽管保存了这么多的 superblock,但是文件系统只使用第一个块组即 Group0 中超级块信息来获取文件系统属性,只有当 Group0 上的 superblock 损坏或丢失才会找下一个备份超级块复制到 Group0 中来恢复文件系统。</p>
<p>下图是一个 ext4 文件系统的 superblock 的信息,ext 家族的文件系统都能使用 dumpe2fs -h 获取。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615094025275-1008363481.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615094025275-1008363481.jpg" alt="">
</a></p>
<h2 id="块组描述符表-gdt-块组描述符表gdt块组描述符表-gdt"><a href="#%E5%9D%97%E7%BB%84%E6%8F%8F%E8%BF%B0%E7%AC%A6%E8%A1%A8-GDT" title="块组描述符表(GDT)"></a>块组描述符表 (GDT)<a class="td-heading-self-link" href="#%e5%9d%97%e7%bb%84%e6%8f%8f%e8%bf%b0%e7%ac%a6%e8%a1%a8-gdt-%e5%9d%97%e7%bb%84%e6%8f%8f%e8%bf%b0%e7%ac%a6%e8%a1%a8gdt%e5%9d%97%e7%bb%84%e6%8f%8f%e8%bf%b0%e7%ac%a6%e8%a1%a8-gdt" aria-label="Heading self-link"></a></h2>
<p>既然文件系统划分了块组,那么每个块组的信息和属性元数据又保存在哪里呢?</p>
<p>ext 文件系统每一个块组信息使用 32 字节描述,这 32 个字节称为块组描述符,所有块组的块组描述符组成块组描述符表 GDT (group descriptor table)。</p>
<p>虽然每个块组都需要块组描述符来记录块组的信息和属性元数据,但是不是每个块组中都存放了块组描述符。ext 文件系统的存储方式是:将它们组成一个 GDT,并将该 GDT 存放于某些块组中,存放 GDT 的块组和存放 superblock 和备份 superblock 的块相同,也就是说它们是同时出现在某一个块组中的。读取时也总是读取 Group0 中的块组描述符表信息。</p>
<p>假如 block 大小为 4KB 的文件系统划分了 143 个块组,每个块组描述符 32 字节,那么 GDT 就需要 143*32=4576 字节即两个 block 来存放。这两个 GDT block 中记录了所有块组的块组信息,且存放 GDT 的块组中的 GDT 都是完全相同的。</p>
<p>下图是一个块组描述符的信息 (通过 dumpe2fs 获取)。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615094101525-344682252.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615094101525-344682252.jpg" alt="">
</a></p>
<h2 id="保留gdt-reserved-gdt-保留gdtreserved-gdt保留-gdt-reserved-gdt"><a href="#%E4%BF%9D%E7%95%99GDT-Reserved-GDT" title="保留GDT(Reserved GDT)"></a>保留 GDT (Reserved GDT)<a class="td-heading-self-link" href="#%e4%bf%9d%e7%95%99gdt-reserved-gdt-%e4%bf%9d%e7%95%99gdtreserved-gdt%e4%bf%9d%e7%95%99-gdt-reserved-gdt" aria-label="Heading self-link"></a></h2>
<p>保留 GDT 用于以后扩容文件系统使用,防止扩容后块组太多,使得块组描述符超出当前存储 GDT 的 blocks。保留 GDT 和 GDT 总是同时出现,当然也就和 superblock 同时出现了。</p>
<p>例如前面 143 个块组使用了 2 个 block 来存放 GDT,但是此时第二个 block 还空余很多空间,当扩容到一定程度时 2 个 block 已经无法再记录块组描述符了,这时就需要分配一个或多个 Reserved GDT 的 block 来存放超出的块组描述符。</p>
<p>由于新增加了 GDT block,所以应该让每一个保存 GDT 的块组都同时增加这一个 GDT block,所以将保留 GDT 和 GDT 存放在同一个块组中可以直接将保留 GDT 变换为 GDT 而无需使用低效的复制手段备份到每个存放 GDT 的块组。</p>
<p>同理,新增加了 GDT 需要修改每个块组中 superblock 中的文件系统属性,所以将 superblock 和 Reserved GDT/GDT 放在一起又能提升效率。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20180727160431015-373938073.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20180727160431015-373938073.jpg" alt="">
</a></p>
<p>如上图,除了 Data Blocks 其他的部分都解释过了。data block 是直接存储数据的 block,但事实上并非如此简单。</p>
<p>数据所占用的 block 由文件对应 inode 记录中的 block 指针找到,不同的文件类型,数据 block 中存储的内容是不一样的。以下是 Linux 中不同类型文件的存储方式。</p>
<ul>
<li>对于常规文件,文件的数据正常存储在数据块中。</li>
<li>对于目录,该目录下的所有文件和一级子目录的目录名存储在数据块中。
<ul>
<li><strong>文件名和 inode 号不是存储在其自身的 inode 中,而是存储在其所在目录的 data block 中。</strong></li>
</ul>
</li>
<li>对于符号链接,如果目标路径名较短则直接保存在 inode 中以便更快地查找,如果目标路径名较长则分配一个数据块来保存。</li>
<li>设备文件、FIFO 和 socket 等特殊文件没有数据块,设备文件的主设备号和次设备号保存在 inode 中。</li>
</ul>
<p>常规文件的存储就不解释了,下面分别解释特殊文件的存储方式。</p>
<h2 id="目录文件的data-block-目录文件的data-block目录文件的-data-block"><a href="#%E7%9B%AE%E5%BD%95%E6%96%87%E4%BB%B6%E7%9A%84data-block" title="目录文件的data block"></a>目录文件的 data block<a class="td-heading-self-link" href="#%e7%9b%ae%e5%bd%95%e6%96%87%e4%bb%b6%e7%9a%84data-block-%e7%9b%ae%e5%bd%95%e6%96%87%e4%bb%b6%e7%9a%84data-block%e7%9b%ae%e5%bd%95%e6%96%87%e4%bb%b6%e7%9a%84-data-block" aria-label="Heading self-link"></a></h2>
<p>目录的 data block 的内容如下图所示。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20191005161844399-1089052435.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20191005161844399-1089052435.jpg" alt="">
</a></p>
<p>由图可知,在目录文件的数据块中存储了其下的文件名、目录名、目录本身的相对名称”.” 和上级目录的相对名称”..”,还存储了这些文件名对应的 inode 号、目录项长度 rec_len、文件名长度 name_len 和文件类型 file_type。注意到除了文件本身的 inode 记录了文件类型,其所在的目录的数据块也记录了文件类型。由于 rec_len 只能是 4 的倍数,所以需要使用”\0” 来填充 name_len 不够凑满 4 倍数的部分。至于 rec_len 具体是什么,只需知道它是一种偏移即可。</p>
<p><strong>需要注意的是,inode table 中的 inode 自身并没有存储每个 inode 的 inode 号,它是存储在目录的 data block 中的,通过 inode 号可以计算并索引到 inode table 中该 inode 号对应的 inode 记录,可以认为这个 inode 号是一个 inode 指针</strong> (当然,并非真的是指针,但有助于理解通过 inode 号索引找到对应 inode 的这个过程,后文将在需要的时候使用 inode 指针这个词来表示 inode 号。至此,已经知道了两种指针:一种是 inode table 中每个 inode 记录指向其对应 data block 的 block 指针,一个此处的『inode 指针』)。</p>
<p>除了 inode 号,目录的 data block 中还使用数字格式记录了文件类型,数字格式和文件类型的对应关系如下图。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615094424884-1119563692.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615094424884-1119563692.jpg" alt="">
</a></p>
<p>注意到目录的 data block 中前两行存储的是目录本身的相对名称”.” 和上级目录的相对名称”..”,它们实际上是目录本身的硬链接和上级目录的硬链接。硬链接的本质后面说明。</p>
<h2 id="如何根据inode号找到inode-如何根据inode号找到inode如何根据-inode-号找到-inode"><a href="#%E5%A6%82%E4%BD%95%E6%A0%B9%E6%8D%AEinode%E5%8F%B7%E6%89%BE%E5%88%B0inode" title="如何根据inode号找到inode"></a>如何根据 inode 号找到 inode<a class="td-heading-self-link" href="#%e5%a6%82%e4%bd%95%e6%a0%b9%e6%8d%aeinode%e5%8f%b7%e6%89%be%e5%88%b0inode-%e5%a6%82%e4%bd%95%e6%a0%b9%e6%8d%aeinode%e5%8f%b7%e6%89%be%e5%88%b0inode%e5%a6%82%e4%bd%95%e6%a0%b9%e6%8d%ae-inode-%e5%8f%b7%e6%89%be%e5%88%b0-inode" aria-label="Heading self-link"></a></h2>
<p>前面提到过,inode 结构自身并没有保存 inode 号(同样,也没有保存文件名),那么 inode 号保存在哪里呢?目录的 data block 中保存了该目录中每个文件的 inode 号。</p>
<p>另一个问题,既然 inode 中没有 inode 号,那么如何根据目录 data block 中的 inode 号找到 inode table 中对应的 inode 呢?</p>
<p>实际上,只要有了 inode 号,就可以计算出 inode 表中对应该 inode 号的 inode 结构。在创建文件系统的时候,每个块组中的起始 inode 号以及 inode table 的起始地址都已经确定了,所以只要知道 inode 号,就能知道这个 inode 号和该块组起始 inode 号的偏移数量,再根据每个 inode 结构的大小 (256 字节或其它大小),就能计算出来对应的 inode 结构。</p>
<p>所以,目录的 data block 中的 inode number 和 inode table 中的 inode 是通过计算的方式一一映射起来的。从另一个角度上看,目录 data block 中的 inode number 是找到 inode table 中对应 inode 记录的唯一方式。</p>
<p>考虑一种比较特殊的情况:目录 data block 的记录已经删除,但是该记录对应的 inode 结构仍然存在于 inode table 中。这种 inode 称为孤儿 inode(orphan inode):存在于 inode table 中,但却无法再索引到它。因为目录中已经没有该 inode 对应的文件记录了,所以其它进程将无法找到该 inode,也就无法根据该 inode 找到该文件之前所占用的 data block,这正是创建便删除所实现的真正临时文件,该临时文件只有当前进程和子进程才能访问。</p>
<h2 id="符号链接存储方式-符号链接存储方式符号链接存储方式"><a href="#%E7%AC%A6%E5%8F%B7%E9%93%BE%E6%8E%A5%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F" title="符号链接存储方式"></a>符号链接存储方式<a class="td-heading-self-link" href="#%e7%ac%a6%e5%8f%b7%e9%93%be%e6%8e%a5%e5%ad%98%e5%82%a8%e6%96%b9%e5%bc%8f-%e7%ac%a6%e5%8f%b7%e9%93%be%e6%8e%a5%e5%ad%98%e5%82%a8%e6%96%b9%e5%bc%8f%e7%ac%a6%e5%8f%b7%e9%93%be%e6%8e%a5%e5%ad%98%e5%82%a8%e6%96%b9%e5%bc%8f" aria-label="Heading self-link"></a></h2>
<p>符号链接即为软链接,类似于 Windows 操作系统中的快捷方式,它的作用是指向原文件或目录。</p>
<p>软链接之所以也被称为特殊文件的原因是:它一般情况下不占用 data block,仅仅通过它对应的 inode 记录就能将其信息描述完成;符号链接的大小是其指向目标路径占用的字符个数,例如某个符号链接的指向方式为”rmt –&gt; ../sbin/rmt”,则其文件大小为 11 字节;只有当符号链接指向的目标的路径名较长 (60 个字节) 时文件系统才会划分一个 data block 给它;它的权限如何也不重要,因它只是一个指向原文件的” 工具”,最终决定是否能读写执行的权限由原文件决定,所以很可能 ls -l 查看到的符号链接权限为 777。</p>
<p>注意,软链接的 block 指针存储的是目标文件名。也就是说,链接文件的一切都依赖于其目标文件名。这就解释了为什么 /mnt 的软链接 /tmp/mnt 在 /mnt 挂载文件系统后,通过软链接就能进入 /mnt 所挂载的文件系统。究其原因,还是因为其目标文件名”/mnt” 并没有改变。</p>
<p>例如以下筛选出了 /etc/ 下的符号链接,注意观察它们的权限和它们占用的空间大小。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@xuexi ~]# ll /etc/ | grep &#39;^l&#39;
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 56 Feb 18 2016 favicon.png -&gt; /usr/share/icons/hicolor/16x16/apps/system-logo-icon.png
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 22 Feb 18 2016 grub.conf -&gt; ../boot/grub/grub.conf
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 11 Feb 18 2016 init.d -&gt; rc.d/init.d
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 7 Feb 18 2016 rc -&gt; rc.d/rc
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 10 Feb 18 2016 rc0.d -&gt; rc.d/rc0.d
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 10 Feb 18 2016 rc1.d -&gt; rc.d/rc1.d
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 10 Feb 18 2016 rc2.d -&gt; rc.d/rc2.d
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 10 Feb 18 2016 rc3.d -&gt; rc.d/rc3.d
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 10 Feb 18 2016 rc4.d -&gt; rc.d/rc4.d
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 10 Feb 18 2016 rc5.d -&gt; rc.d/rc5.d
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 10 Feb 18 2016 rc6.d -&gt; rc.d/rc6.d
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 13 Feb 18 2016 rc.local -&gt; rc.d/rc.local
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 15 Feb 18 2016 rc.sysinit -&gt; rc.d/rc.sysinit
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 14 Feb 18 2016 redhat-release -&gt; centos-release
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 11 Apr 10 2016 rmt -&gt; ../sbin/rmt
</span></span><span style="display:flex;"><span>lrwxrwxrwx. 1 root root 14 Feb 18 2016 system-release -&gt; centos-release
</span></span></code></pre></div><h2 id="设备文件fifo套接字文件-设备文件fifo套接字文件设备文件fifo套接字文件"><a href="#%E8%AE%BE%E5%A4%87%E6%96%87%E4%BB%B6%E3%80%81FIFO%E3%80%81%E5%A5%97%E6%8E%A5%E5%AD%97%E6%96%87%E4%BB%B6" title="设备文件、FIFO、套接字文件"></a>设备文件、FIFO、套接字文件<a class="td-heading-self-link" href="#%e8%ae%be%e5%a4%87%e6%96%87%e4%bb%b6fifo%e5%a5%97%e6%8e%a5%e5%ad%97%e6%96%87%e4%bb%b6-%e8%ae%be%e5%a4%87%e6%96%87%e4%bb%b6fifo%e5%a5%97%e6%8e%a5%e5%ad%97%e6%96%87%e4%bb%b6%e8%ae%be%e5%a4%87%e6%96%87%e4%bb%b6fifo%e5%a5%97%e6%8e%a5%e5%ad%97%e6%96%87%e4%bb%b6" aria-label="Heading self-link"></a></h2>
<p>关于这 3 种文件类型的文件只需要通过 inode 就能完全保存它们的信息,它们不占用任何数据块,所以它们是特殊文件。</p>
<p>设备文件的主设备号和次设备号也保存在 inode 中。以下是 /dev/ 下的部分设备信息。注意到它们的第 5 列和第 6 列信息,它们分别是主设备号和次设备号,主设备号标识每一种设备的类型,次设备号标识同种设备类型的不同编号;也注意到这些信息中没有大小的信息,因为设备文件不占用数据块所以没有大小的概念。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@xuexi ~]# ll /dev | tail
</span></span><span style="display:flex;"><span>crw-rw---- 1 vcsa tty 7, 129 Oct 7 21:26 vcsa1
</span></span><span style="display:flex;"><span>crw-rw---- 1 vcsa tty 7, 130 Oct 7 21:27 vcsa2
</span></span><span style="display:flex;"><span>crw-rw---- 1 vcsa tty 7, 131 Oct 7 21:27 vcsa3
</span></span><span style="display:flex;"><span>crw-rw---- 1 vcsa tty 7, 132 Oct 7 21:27 vcsa4
</span></span><span style="display:flex;"><span>crw-rw---- 1 vcsa tty 7, 133 Oct 7 21:27 vcsa5
</span></span><span style="display:flex;"><span>crw-rw---- 1 vcsa tty 7, 134 Oct 7 21:27 vcsa6
</span></span><span style="display:flex;"><span>crw-rw---- 1 root root 10, 63 Oct 7 21:26 vga_arbiter
</span></span><span style="display:flex;"><span>crw------- 1 root root 10, 57 Oct 7 21:26 vmci
</span></span><span style="display:flex;"><span>crw-rw-rw- 1 root root 10, 56 Oct 7 21:27 vsock
</span></span><span style="display:flex;"><span>crw-rw-rw- 1 root root 1, 5 Oct 7 21:26 zero
</span></span></code></pre></div><p>每个文件都有一个 inode,在将 inode 关联到文件后系统将通过 inode 号来识别文件,而不是文件名。并且访问文件时将先找到 inode,通过 inode 中记录的 block 位置找到该文件。</p>
<h2 id="硬链接-硬链接硬链接"><a href="#%E7%A1%AC%E9%93%BE%E6%8E%A5" title="硬链接"></a>硬链接<a class="td-heading-self-link" href="#%e7%a1%ac%e9%93%be%e6%8e%a5-%e7%a1%ac%e9%93%be%e6%8e%a5%e7%a1%ac%e9%93%be%e6%8e%a5" aria-label="Heading self-link"></a></h2>
<p>虽然每个文件都有一个 inode,但是存在一种可能:多个文件的 inode 相同,也就即 inode 号、元数据、block 位置都相同,这是一种什么样的情况呢?能够想象这些 inode 相同的文件使用的都是同一条 inode 记录,所以代表的都是同一个文件,这些文件所在目录的 data block 中的 inode 号都是一样的,只不过各 inode 号对应的文件名互不相同而已。这种 inode 相同的文件在 Linux 中被称为” 硬链接”。</p>
<p>硬链接文件的 inode 都相同,每个文件都有一个” 硬链接数” 的属性,使用 ls -l 的第二列就是被硬链接数,它表示的就是该文件有几个硬链接。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@xuexi ~]# ls -l
</span></span><span style="display:flex;"><span>total 48
</span></span><span style="display:flex;"><span>drwxr-xr-x 5 root root 4096 Oct 15 18:07 700
</span></span><span style="display:flex;"><span>-rw-------. 1 root root 1082 Feb 18 2016 anaconda-ks.cfg
</span></span><span style="display:flex;"><span>-rw-r--r-- 1 root root 399 Apr 29 2016 Identity.pub
</span></span><span style="display:flex;"><span>-rw-r--r--. 1 root root 21783 Feb 18 2016 install.log
</span></span><span style="display:flex;"><span>-rw-r--r--. 1 root root 6240 Feb 18 2016 install.log.syslog
</span></span></code></pre></div><p>例如下图描述的是 dir1 目录中的文件 name1 及其硬链接 dir2/name2,右边分别是它们的 inode 和 data block。这里也看出了硬链接文件之间唯一不同的就是其所在目录中的记录不同。注意下图中有一列 Link Count 就是标记硬链接数的属性。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20191005170242909-1745674821.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20191005170242909-1745674821.jpg" alt="">
</a></p>
<p>每创建一个文件的硬链接,实质上是多一个指向该 inode 记录的 inode 指针,并且硬链接数加 1。</p>
<p>删除文件的实质是删除该文件所在目录 data block 中的对应的 inode 行,所以也是减少硬链接次数,由于 block 指针是存储在 inode 中的,所以不是真的删除数据,如果仍有其他 inode 号链接到该 inode,那么该文件的 block 指针仍然是可用的。当硬链接次数为 1 时再删除文件就是真的删除文件了,此时 inode 记录中 block 指针也将被删除。</p>
<p><strong>不能跨分区创建硬链接</strong>,因为不同文件系统的 inode 号可能会相同,如果允许创建硬链接,复制到另一个分区时 inode 可能会和此分区已使用的 inode 号冲突。</p>
<p><strong>硬链接只能对文件创建,无法对目录创建硬链接</strong>。之所以无法对目录创建硬链接,是因为文件系统已经把每个目录的硬链接创建好了,它们就是相对路径中的”.” 和”..”,分别标识当前目录的硬链接和上级目录的硬链接。每一个目录中都会包含这两个硬链接,它包含了两个信息:(1) 一个没有子目录的目录文件的硬链接数是 2,其一是目录本身,即该目录 datablock 中的”.”,其二是其父目录 datablock 中该目录的记录,这两者都指向同一个 inode 号;(2) 一个包含子目录的目录文件,其硬链接数是 2 + 子目录数,因为每个子目录都关联一个父目录的硬链接”..”。很多人在计算目录的硬链接数时认为由于包含了”.” 和”..”,所以空目录的硬链接数是 2,这是错误的,因为”..” 不是本目录的硬链接。另外,还有一个特殊的目录应该纳入考虑,即”/“目录,它自身是一个文件系统的入口,是自引用 (下文中会解释自引用) 的,所以”/“目录下的”.” 和”..” 的 inode 号相同,它自身不占用硬链接,因为其 datablock 中只记录 inode 号相同的”.” 和”..”,不再像其他目录一样还记录一个名为”/“的目录,所以”/“的硬链接数也是 2 + 子目录数,但这个 2 是”.” 和”..” 的结果。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@xuexi ~]# ln /tmp /mydata
</span></span><span style="display:flex;"><span>ln: `/tmp&#39;: hard link not allowed for directory
</span></span></code></pre></div><p>为什么文件系统自己创建好了目录的硬链接就不允许人为创建呢?从”.” 和”..” 的用法上考虑,如果当前目录为 /usr,我们可以使用”./local” 来表示 /usr/local,但是如果我们人为创建了 /usr 目录的硬链接 /tmp/husr,难道我们也要使用”/tmp/husr/local” 来表示 /usr/local 吗?这其实已经是软链接的作用了。若要将其认为是硬链接的功能,这必将导致硬链接维护的混乱。</p>
<p>不过,通过 mount 工具的”–bind” 选项,可以将一个目录挂载到另一个目录下,实现伪” 硬链接”,它们的内容和 inode 号是完全相同的。</p>
<p>硬链接的创建方法:<code>ln file_target link_name</code>。</p>
<h2 id="软链接-软链接软链接"><a href="#%E8%BD%AF%E9%93%BE%E6%8E%A5" title="软链接"></a>软链接<a class="td-heading-self-link" href="#%e8%bd%af%e9%93%be%e6%8e%a5-%e8%bd%af%e9%93%be%e6%8e%a5%e8%bd%af%e9%93%be%e6%8e%a5" aria-label="Heading self-link"></a></h2>
<p>软链接就是字符链接,链接文件默认指的就是字符链接文件 (注意不是字符设备),使用”l” 表示其类型。</p>
<p>硬链接不能跨文件系统创建,否则 inode 号可能会冲突。于是实现了软链接以便跨文件系统建立链接。既然是跨文件系统,那么软链接必须得有自己的 inode 号。</p>
<p>软链接在功能上等价与 Windows 系统中的快捷方式,它指向原文件,原文件损坏或消失,软链接文件就损坏。<strong>可以认为软链接 inode 记录中的指针内容是目标路径的字符串</strong>。</p>
<p>创建方式:<code>ln –s source_file softlink_name</code>,记住是 <code>source_file&lt;--link_name</code> 的指向关系 (反箭头),以前我老搞错位置。</p>
<p>查看软链接的值:<code>readlink softlink_name</code></p>
<p>在设置软链接的时候,source_file 虽然不要求是绝对路径,但建议给绝对路径。是否还记得软链接文件的大小?它是根据软链接所指向路径的字符数计算的,例如某个符号链接的指向方式为”rmt –&gt; ../sbin/rmt”,它的文件大小为 11 字节,也就是说只要建立了软链接后,软链接的指向路径是不会改变的,仍然是”../sbin/rmt”。如果此时移动软链接文件本身,它的指向是不会改变的,仍然是 11 个字符的”../sbin/rmt”,但此时该软链接父目录下可能根本就不存在 /sbin/rmt,也就是说此时该软链接是一个被破坏的软链接。</p>
<h2 id="inode大小和划分-inode大小和划分inode-大小和划分"><a href="#inode%E5%A4%A7%E5%B0%8F%E5%92%8C%E5%88%92%E5%88%86" title="inode大小和划分"></a>inode 大小和划分<a class="td-heading-self-link" href="#inode%e5%a4%a7%e5%b0%8f%e5%92%8c%e5%88%92%e5%88%86-inode%e5%a4%a7%e5%b0%8f%e5%92%8c%e5%88%92%e5%88%86inode-%e5%a4%a7%e5%b0%8f%e5%92%8c%e5%88%92%e5%88%86" aria-label="Heading self-link"></a></h2>
<p>inode 大小为 128 字节的倍数,最小为 128 字节。它有默认值大小,它的默认值由 /etc/mke2fs.conf 文件中指定。不同的文件系统默认值可能不同。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@xuexi ~]# cat /etc/mke2fs.conf
</span></span><span style="display:flex;"><span>[defaults]
</span></span><span style="display:flex;"><span> base_features = sparse_super,filetype,resize_inode,dir_index,ext_attr
</span></span><span style="display:flex;"><span> enable_periodic_fsck = 1
</span></span><span style="display:flex;"><span> blocksize = 4096
</span></span><span style="display:flex;"><span> inode_size = 256
</span></span><span style="display:flex;"><span> inode_ratio = 16384
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[fs_types]
</span></span><span style="display:flex;"><span> ext3 = {
</span></span><span style="display:flex;"><span> features = has_journal
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> ext4 = {
</span></span><span style="display:flex;"><span> features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize
</span></span><span style="display:flex;"><span> inode_size = 256
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>同样观察到这个文件中还记录了 blocksize 的默认值和 inode 分配比率 inode_ratio。inode_ratio=16384 表示每 16384 个字节即 16KB 就分配一个 inode 号,由于默认 blocksize=4KB,所以每 4 个 block 就分配一个 inode 号。当然分配的这些 inode 号只是预分配,并不真的代表会全部使用,毕竟每个文件才会分配一个 inode 号。但是分配的 inode 自身会占用 block,而且其自身大小 256 字节还不算小,所以 inode 号的浪费代表着空间的浪费。</p>
<p>既然知道了 inode 分配比率,就能计算出每个块组分配多少个 inode 号,也就能计算出 inode table 占用多少个 block。</p>
<p>如果文件系统中大量存储电影等大文件,inode 号就浪费很多,inode 占用的空间也浪费很多。但是没办法,文件系统又不知道你这个文件系统是用来存什么样的数据,多大的数据,多少数据。</p>
<p>当然 inode size、inode 分配比例、block size 都可以在创建文件系统的时候人为指定。</p>
<h2 id="ext文件系统预留的inode号-ext文件系统预留的inode号ext-文件系统预留的-inode-号"><a href="#ext%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E9%A2%84%E7%95%99%E7%9A%84inode%E5%8F%B7" title="ext文件系统预留的inode号"></a>ext 文件系统预留的 inode 号<a class="td-heading-self-link" href="#ext%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e9%a2%84%e7%95%99%e7%9a%84inode%e5%8f%b7-ext%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e9%a2%84%e7%95%99%e7%9a%84inode%e5%8f%b7ext-%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e9%a2%84%e7%95%99%e7%9a%84-inode-%e5%8f%b7" aria-label="Heading self-link"></a></h2>
<p>Ext 预留了一些 inode 做特殊特性使用,如下:某些可能并非总是准确,具体的 inode 号对应什么文件可以使用”find /-inum NUM” 查看。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>Ext4的特殊inode
</span></span><span style="display:flex;"><span>Inode号 用途
</span></span><span style="display:flex;"><span>0 不存在0号inode,可用于标识目录data block中已删除的文件
</span></span><span style="display:flex;"><span>1 虚拟文件系统,如/proc和/sys
</span></span><span style="display:flex;"><span>2 根目录 # 注意此行
</span></span><span style="display:flex;"><span>3 ACL索引
</span></span><span style="display:flex;"><span>4 ACL数据
</span></span><span style="display:flex;"><span>5 Boot loader
</span></span><span style="display:flex;"><span>6 未删除的目录
</span></span><span style="display:flex;"><span>7 预留的块组描述符inode
</span></span><span style="display:flex;"><span>8 日志inode
</span></span><span style="display:flex;"><span>11 第一个非预留的inode,通常是lost+found目录
</span></span></code></pre></div><p>所以在 ext4 文件系统的 dumpe2fs 信息中,能观察到 fisrt inode 号可能为 11 也可能为 12。</p>
<p>并且注意到”/“的 inode 号为 2,这个特性在文件访问时会用上。</p>
<p>需要注意的是,每个文件系统都会分配自己的 inode 号,不同文件系统之间是可能会出现使用相同 inode 号文件的。例如:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@xuexi ~]# find / -ignore_readdir_race -inum 2 -ls
</span></span><span style="display:flex;"><span> 2 4 dr-xr-xr-x 22 root root 4096 Jun 9 09:56 /
</span></span><span style="display:flex;"><span> 2 2 dr-xr-xr-x 5 root root 1024 Feb 25 11:53 /boot
</span></span><span style="display:flex;"><span> 2 0 c--------- 1 root root Jun 7 02:13 /dev/pts/ptmx
</span></span><span style="display:flex;"><span> 2 0 -rw-r--r-- 1 root root 0 Jun 6 18:13 /proc/sys/fs/binfmt_misc/status
</span></span><span style="display:flex;"><span> 2 0 drwxr-xr-x 3 root root 0 Jun 6 18:13 /sys/fs
</span></span></code></pre></div><p>从结果中可见,除了根的 Inode 号为 2,还有几个文件的 inode 号也是 2,它们都属于独立的文件系统,有些是虚拟文件系统,如 /proc 和 /sys。</p>
<h2 id="ext2-3的inode直接间接寻址-ext23的inode直接间接寻址ext23-的-inode-直接间接寻址"><a href="#ext2-3%E7%9A%84inode%E7%9B%B4%E6%8E%A5%E3%80%81%E9%97%B4%E6%8E%A5%E5%AF%BB%E5%9D%80" title="ext2/3的inode直接、间接寻址"></a>ext2/3 的 inode 直接、间接寻址<a class="td-heading-self-link" href="#ext2-3%e7%9a%84inode%e7%9b%b4%e6%8e%a5%e9%97%b4%e6%8e%a5%e5%af%bb%e5%9d%80-ext23%e7%9a%84inode%e7%9b%b4%e6%8e%a5%e9%97%b4%e6%8e%a5%e5%af%bb%e5%9d%80ext23-%e7%9a%84-inode-%e7%9b%b4%e6%8e%a5%e9%97%b4%e6%8e%a5%e5%af%bb%e5%9d%80" aria-label="Heading self-link"></a></h2>
<p>前文说过,inode 中保存了 blocks 指针,但是一条 inode 记录中能保存的指针数量是有限的,否则就会超出 inode 大小 (128 字节或 256 字节)。</p>
<p>在 ext2 和 ext3 文件系统中,一个 inode 中最多只能有 15 个指针,每个指针使用 i_block [n] 表示。</p>
<p>前 12 个指针 i_block [0] 到 i_block [11] 是直接寻址指针,每个指针指向一个数据区的 block。如下图所示。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615095614790-1724952851.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615095614790-1724952851.jpg" alt="">
</a></p>
<p>第 13 个指针 i_block [12] 是一级间接寻址指针,它指向一个仍然存储了指针的 block 即 i_block [12] –&gt; Pointerblock –&gt; datablock。</p>
<p>第 14 个指针 i_block [13] 是二级间接寻址指针,它指向一个仍然存储了指针的 block,但是这个 block 中的指针还继续指向其他存储指针的 block,即 i_block [13] –&gt; Pointerblock1 –&gt; PointerBlock2 –&gt; datablock。</p>
<p>第 15 个指针 i_block [14] 是三级间接寻址指针,它指向一个任然存储了指针的 block,这个指针 block 下还有两次指针指向。即 i_block [13] –&gt; Pointerblock1 –&gt; PointerBlock2 –&gt; PointerBlock3 –&gt; datablock。</p>
<p>其中由于每个指针大小为 4 字节,所以每个指针 block 能存放的指针数量为 BlockSize/4byte。例如 blocksize 为 4KB,那么一个 Block 可以存放 4096/4=1024 个指针。</p>
<p>如下图。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615095634665-801241861.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615095634665-801241861.jpg" alt="">
</a></p>
<p>为什么要分间接和直接指针呢?如果一个 inode 中 15 个指针全是直接指针,假如每个 block 的大小为 1KB,那么 15 个指针只能指向 15 个 block 即 15KB 的大小,由于每个文件对应一个 inode 号,所以就限制了每个文件最大为 15*1=15KB,这显然是不合理的。</p>
<p>如果存储大于 15KB 的文件而又不太大的时候,就占用一级间接指针 i_block [12],这时可以存放指针数量为 1024/4+12=268,所以能存放 268KB 的文件。</p>
<p>如果存储大于 268K 的文件而又不太大的时候,就继续占用二级指针 i_block [13],这时可以存放指针数量为 [1024/4]^2+1024/4+12=65804,所以能存放 65804KB=64M 左右的文件。</p>
<p>如果存放的文件大于 64M,那么就继续使用三级间接指针 i_block [14],存放的指针数量为 [1024/4]^3+[1024/4]^2+[1024/4]+12=16843020 个指针,所以能存放 16843020KB=16GB 左右的文件。</p>
<p>如果 blocksize=4KB 呢?那么最大能存放的文件大小为 ([4096/4]^3+[4096/4]^2+[4096/4]+12)*4/1024/1024/1024=4T 左右。</p>
<p>当然这样计算出来的不一定就是最大能存放的文件大小,它还受到另一个条件的限制。这里的计算只是表明一个大文件是如何寻址和分配的。</p>
<p>其实看到这里的计算数值,就知道 ext2 和 ext3 对超大文件的存取效率是低下的,它要核对太多的指针,特别是 4KB 大小的 blocksize 时。而 ext4 针对这一点就进行了优化,ext4 使用 extent 的管理方式取代 ext2 和 ext3 的块映射,大大提高了效率也降低了碎片。</p>
<p>在 Linux 上执行删除、复制、重命名、移动等操作时,它们是怎么进行的呢?还有访问文件时是如何找到它的呢?其实只要理解了前文中介绍的几个术语以及它们的作用就很容易知道文件操作的原理了。</p>
<p>注:在这一小节所解释的都是在单个文件系统下的行为,在多个文件系统中如何请看下一个小节:多文件系统关联。</p>
<h2 id="读取文件-读取文件读取文件"><a href="#%E8%AF%BB%E5%8F%96%E6%96%87%E4%BB%B6" title="读取文件"></a>读取文件<a class="td-heading-self-link" href="#%e8%af%bb%e5%8f%96%e6%96%87%e4%bb%b6-%e8%af%bb%e5%8f%96%e6%96%87%e4%bb%b6%e8%af%bb%e5%8f%96%e6%96%87%e4%bb%b6" aria-label="Heading self-link"></a></h2>
<p>当执行 <code>cat /var/log/messages</code> 命令在系统内部进行了什么样的步骤呢?该命令能被成功执行涉及了 cat 命令的寻找、权限判断以及 messages 文件的寻找和权限判断等等复杂的过程。这里只解释和本节内容相关的如何寻找到被 cat 的 /var/log/messages 文件。</p>
<ul>
<li><strong>找到根文件系统的块组描述符表所在的 blocks,读取 GDT (已在内存中) 找到 inode table 的 block 号。</strong></li>
</ul>
<p>因为 GDT 总是和 superblock 在同一个块组,而 superblock 总是在分区的第 1024-2047 个字节,所以很容易就知道第一个 GDT 所在的块组以及 GDT 在这个块组中占用了哪些 block。</p>
<p>其实 GDT 早已经在内存中了,在系统开机的时候会挂载根文件系统,挂载的时候就已经将所有的 GDT 放进内存中。</p>
<ul>
<li><strong>在 inode table 的 block 中定位到根”/“的 inode,找出”/“指向的 data block。</strong></li>
</ul>
<p>前文说过,ext 文件系统预留了一些 inode 号,其中”/“的 inode 号为 2,所以可以根据 inode 号直接定位根目录文件的 data block。</p>
<ul>
<li><strong>在”/“的 datablock 中记录了 var 目录名和 var 的 inode 号,找到该 inode 记录,inode 记录中存储了指向 var 的 block 指针,所以也就找到了 var 目录文件的 data block。</strong></li>
</ul>
<p>通过 var 目录的 inode 号,可以寻找到 var 目录的 inode 记录,但是在寻找的过程中,还需要知道该 inode 记录所在的块组以及所在的 inode table,所以需要读取 GDT,同样,GDT 已经缓存到了内存中。</p>
<ul>
<li><strong>在 var 的 data block 中记录了 log 目录名和其 inode 号,通过该 inode 号定位到该 inode 所在的块组及所在的 inode table,并根据该 inode 记录找到 log 的 data block。</strong></li>
<li><strong>在 log 目录文件的 data block 中记录了 messages 文件名和对应的 inode 号,通过该 inode 号定位到该 inode 所在的块组及所在的 inode table,并根据该 inode 记录找到 messages 的 data block。</strong></li>
<li><strong>最后读取 messages 对应的 datablock。</strong></li>
</ul>
<p>将上述步骤中 GDT 部分的步骤简化后比较容易理解。如下:找到 GDT–&gt; 找到”/“的 inode–&gt; 找到 / 的数据块读取 var 的 inode–&gt; 找到 var 的数据块读取 log 的 inode–&gt; 找到 log 的数据块读取 messages 的 inode–&gt; 找到 messages 的数据块并读取它们。</p>
<p>当然,在每次定位到 inode 记录后,都会先将 inode 记录加载到内存中,然后查看权限,如果权限允许,将根据 block 指针找到对应的 data block。</p>
<h2 id="删除重命名和移动文件-删除重命名和移动文件删除重命名和移动文件"><a href="#%E5%88%A0%E9%99%A4%E3%80%81%E9%87%8D%E5%91%BD%E5%90%8D%E5%92%8C%E7%A7%BB%E5%8A%A8%E6%96%87%E4%BB%B6" title="删除、重命名和移动文件"></a>删除、重命名和移动文件<a class="td-heading-self-link" href="#%e5%88%a0%e9%99%a4%e9%87%8d%e5%91%bd%e5%90%8d%e5%92%8c%e7%a7%bb%e5%8a%a8%e6%96%87%e4%bb%b6-%e5%88%a0%e9%99%a4%e9%87%8d%e5%91%bd%e5%90%8d%e5%92%8c%e7%a7%bb%e5%8a%a8%e6%96%87%e4%bb%b6%e5%88%a0%e9%99%a4%e9%87%8d%e5%91%bd%e5%90%8d%e5%92%8c%e7%a7%bb%e5%8a%a8%e6%96%87%e4%bb%b6" aria-label="Heading self-link"></a></h2>
<p>注意这里是不跨越文件系统的操作行为。</p>
<ul>
<li><strong>删除文件分为普通文件和目录文件,知道了这两种类型的文件的删除原理,就知道了其他类型特殊文件的删除方法。</strong></li>
</ul>
<p>对于删除普通文件:(1) 找到文件的 inode 和 data block (根据前一个小节中的方法寻找);(2) 将 inode table 中该 inode 记录中的 data block 指针删除;(3) 在 imap 中将该文件的 inode 号标记为未使用;(4) 在其所在目录的 data block 中将该文件名所在的记录行删除,删除了记录就丢失了指向 inode 的指针(实际上不是真的删除,直接删除的话会在目录 data block 的数据结构中产生空洞,所以实际的操作是将待删除文件的 inode 号设置为特殊的值 0,这样下次新建文件时就可以重用该行记录);(5) 将 bmap 中 data block 对应的 block 号标记为未使用(对于 ext 文件系统,这个步骤可能会导致删除大文件时间较久,资源消耗较多,对于其它文件系统,则视情况而定)。</p>
<p>对于删除目录文件:找到目录和目录下所有文件、子目录、子文件的 inode 和 data block;在 imap 中将这些 inode 号标记为未使用;将 bmap 中将这些文件占用的 block 号标记为未使用;在该目录的父目录的 data block 中将该目录名所在的记录行删除。需要注意的是,删除父目录 data block 中的记录是最后一步,如果该步骤提前,将报目录非空的错误,因为在该目录中还有文件占用。</p>
<p>关于上面的 (2)-(5):当 (2) 中删除 data block 指针后,将无法再找到这个文件的数据;当 (3) 标记 inode 号未使用,表示该 inode 号可以被后续的文件重用;当 (4) 删除目录 data block 中关于该文件的记录,真正的删除文件,外界再也定位也无法看到这个文件了;当 (5) 标记 data block 为未使用后,表示开始释放空间,这些 data block 可以被其他文件重用。</p>
<p>注意,在第 (5) 步之前,由于 data block 还未被标记为未使用,在 superblock 中仍然认为这些 data block 是正在使用中的。这表示尽管文件已经被删除了,但空间却还没有释放,df 也会将其统计到已用空间中 (df 是读取 superblock 中的数据块数量,并计算转换为空间大小)。</p>
<p>什么时候会发生这种情况呢?当一个进程正在引用文件时将该文件删除,就会出现文件已删除但空间未释放的情况。这时步骤已经进行到 (4),外界无法再找到该文件,但由于进程在加载该文件时已经获取到了该文件所有的 data block 指针,该进程可以获取到该文件的所有数据,但却暂时不会释放该文件空间。直到该进程结束,文件系统才将未执行的步骤 (5) 继续完成。这也是为什么有时候 du 的统计结果比 df 小的原因,关于 du 和 df 统计结果的差别,详细内容见:<a href="https://www.junmajinlong.com/linux/du_df">详细分析 du 和 df 的统计结果为什么不一样</a>。</p>
<ul>
<li><strong>重命名文件分为同目录内重命名和非同目录内重命名。非同目录内重命名实际上是移动文件的过程,见下文</strong>。</li>
</ul>
<p>同目录内重命名文件的动作仅仅只是修改所在目录 data block 中该文件记录的文件名部分,不是删除再重建的过程。</p>
<p>如果重命名时有文件名冲突 (该目录内已经存在该文件名),则提示是否覆盖。覆盖的过程是覆盖目录 data block 中冲突文件的记录。例如 /tmp/ 下有 a.txt 和 a.log,若将 a.txt 重命名为 a.log,则提示覆盖,若选择覆盖,则 /tmp 的 data block 中关于 a.log 的记录被覆盖。</p>
<ul>
<li><strong>移动文件</strong></li>
</ul>
<p>同文件系统下移动文件实际上是修改目标文件所在目录的 data block,向其中添加一行指向 inode table 中待移动文件的 inode 记录,如果目标路径下有同名文件,则会提示是否覆盖,实际上是覆盖目录 data block 中冲突文件的记录,由于同名文件的 inode 记录指针被覆盖,所以无法再找到该文件的 data block,也就是说该文件被标记为删除 (如果多个硬链接数,则另当别论)。</p>
<p>所以在同文件系统内移动文件相当快,仅仅在所在目录 data block 中添加或覆盖了一条记录而已。也因此,移动文件时,文件的 inode 号是不会改变的。</p>
<p>对于不同文件系统内的移动,相当于先复制再删除的动作。见后文。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615100156821-861349673.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615100156821-861349673.jpg" alt="">
</a></p>
<p>关于文件移动,在 Linux 环境下有一个非常经典网上却又没任何解释的问题:/tmp/a/a 能覆盖为 /tmp/a 吗?答案是不能,但 windows 能。为什么不能?见 <a href="https://www.junmajinlong.com/linux/linux_file_cmd/#mv_problem">mv 的一个经典问题 (mv 的本质)</a>。</p>
<h2 id="存储和复制文件-存储和复制文件存储和复制文件"><a href="#%E5%AD%98%E5%82%A8%E5%92%8C%E5%A4%8D%E5%88%B6%E6%96%87%E4%BB%B6" title="存储和复制文件"></a>存储和复制文件<a class="td-heading-self-link" href="#%e5%ad%98%e5%82%a8%e5%92%8c%e5%a4%8d%e5%88%b6%e6%96%87%e4%bb%b6-%e5%ad%98%e5%82%a8%e5%92%8c%e5%a4%8d%e5%88%b6%e6%96%87%e4%bb%b6%e5%ad%98%e5%82%a8%e5%92%8c%e5%a4%8d%e5%88%b6%e6%96%87%e4%bb%b6" aria-label="Heading self-link"></a></h2>
<ul>
<li>对于文件存储
<ul>
<li>(1). 读取 GDT,找到各个 (或部分) 块组 imap 中未使用的 inode 号,并为待存储文件分配 inode 号;</li>
<li>(2). 在 inode table 中完善该 inode 号所在行的记录;</li>
<li>(3). 在目录的 data block 中添加一条该文件的相关记录;</li>
<li>(4). 将数据填充到 data block 中。
<ul>
<li>注意,填充到 data block 中的时候会调用 block 分配器:一次分配 4KB 大小的 block 数量,当填充完 4KB 的 data block 后会继续调用 block 分配器分配 4KB 的 block,然后循环直到填充完所有数据。也就是说,如果存储一个 100M 的文件需要调用 block 分配器 100*1024/4=25600 次。</li>
<li>另一方面,在 block 分配器分配 block 时,block 分配器并不知道真正有多少 block 要分配,只是每次需要分配时就分配,在每存储一个 data block 前,就去 bmap 中标记一次该 block 已使用,它无法实现一次标记多个 bmap 位。这一点在 ext4 中进行了优化。</li>
</ul>
</li>
<li>(5) 填充完之后,去 inode table 中更新该文件 inode 记录中指向 data block 的寻址指针。</li>
</ul>
</li>
<li>对于复制,完全就是另一种方式的存储文件。步骤和存储文件的步骤一样。</li>
</ul>
<p>在单个文件系统中的文件操作和多文件系统中的操作有所不同。本文将对此做出非常详细的说明。</p>
<h2 id="根文件系统的特殊性-根文件系统的特殊性根文件系统的特殊性"><a href="#%E6%A0%B9%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E7%9A%84%E7%89%B9%E6%AE%8A%E6%80%A7" title="根文件系统的特殊性"></a>根文件系统的特殊性<a class="td-heading-self-link" href="#%e6%a0%b9%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e7%9a%84%e7%89%b9%e6%ae%8a%e6%80%a7-%e6%a0%b9%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e7%9a%84%e7%89%b9%e6%ae%8a%e6%80%a7%e6%a0%b9%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e7%9a%84%e7%89%b9%e6%ae%8a%e6%80%a7" aria-label="Heading self-link"></a></h2>
<p>这里要明确的是,任何一个文件系统要在 Linux 上能正常使用,必须挂载在某个已经挂载好的文件系统中的某个目录下,例如 /dev/cdrom 挂载在 /mnt 上,/mnt 目录本身是在”/“文件系统下的。而且任意文件系统的一级挂载点必须是在根文件系统的某个目录下,因为只有”/“是自引用的。这里要说明挂载点的级别和自引用的概念。</p>
<p>假如 /dev/sdb1 挂载在 /mydata 上,/dev/cdrom 挂载在 /mydata/cdrom 上,那么 /mydata 就是一级挂载点,此时 /mydata 已经是文件系统 /dev/sdb1 的入口了,而 /dev/cdrom 所挂载的目录 /mydata/cdrom 是文件系统 /dev/sdb1 中的某个目录,那么 /mydata/cdrom 就是二级挂载点。一级挂载点必须在根文件系统下,所以可简述为:文件系统 2 挂载在文件系统 1 中的某个目录下,而文件系统 1 又挂载在根文件系统中的某个目录下。</p>
<p>再解释自引用。首先要说的是,自引用的只能是文件系统,而文件系统表现形式是一个目录,所以自引用是指该目录的 data block 中,”.” 和”..” 的记录中的 inode 号都对应 inode table 中同一个 inode 记录,所以它们 inode 号是相同的,即互为硬链接。而根文件系统是唯一可以自引用的文件系统。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@xuexi /]# ll -ai /
</span></span><span style="display:flex;"><span>total 102
</span></span><span style="display:flex;"><span> 2 dr-xr-xr-x. 22 root root 4096 Jun 6 18:13 .
</span></span><span style="display:flex;"><span> 2 dr-xr-xr-x. 22 root root 4096 Jun 6 18:13 ..
</span></span></code></pre></div><p>由此也能解释 cd /. 和 cd /.. 的结果都还是在根下,这是自引用最直接的表现形式。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@xuexi tmp]# cd /.
</span></span><span style="display:flex;"><span>[root@xuexi /]#
</span></span><span style="display:flex;"><span>[root@xuexi tmp]# cd /..
</span></span><span style="display:flex;"><span>[root@xuexi /]#
</span></span></code></pre></div><p>注意,根目录下的”.” 和”..” 都是”/“目录的硬链接,且其 datablock 中不记录名为”/“的条目,因此除去根目录下子目录数后的硬链接数为 2。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[root@server2 tmp]# a=$(ls -ld / | awk &#39;{print $2}&#39;)
</span></span><span style="display:flex;"><span>[root@server2 tmp]# b=$(ls -l / | grep &#34;^d&#34; |wc -l)
</span></span><span style="display:flex;"><span>[root@server2 tmp]# echo $((a - b))
</span></span><span style="display:flex;"><span>2
</span></span></code></pre></div><h2 id="挂载文件系统的细节-挂载文件系统的细节挂载文件系统的细节"><a href="#%E6%8C%82%E8%BD%BD%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E7%9A%84%E7%BB%86%E8%8A%82" title="挂载文件系统的细节"></a>挂载文件系统的细节<a class="td-heading-self-link" href="#%e6%8c%82%e8%bd%bd%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e7%9a%84%e7%bb%86%e8%8a%82-%e6%8c%82%e8%bd%bd%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e7%9a%84%e7%bb%86%e8%8a%82%e6%8c%82%e8%bd%bd%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e7%9a%84%e7%bb%86%e8%8a%82" aria-label="Heading self-link"></a></h2>
<p>挂载文件系统到某个目录下,例如”mount /dev/cdrom/mnt”,挂载成功后 /mnt 目录中的文件全都暂时不可见了,且挂载后权限和所有者 (如果指定允许普通用户挂载) 等的都改变了,知道为什么吗?</p>
<p>下面就以通过”mount /dev/cdrom/mnt” 为例,详细说明挂载过程中涉及的细节。</p>
<p>在将文件系统 /dev/cdrom (此处暂且认为它是文件系统) 挂载到挂载点 /mnt 之前,挂载点 /mnt 是根文件系统中的一个目录,”/“的 data block 中记录了 /mnt 的一些信息,其中包括 inode 号 inode_n,而在 inode table 中,/mnt 对应的 inode 记录中又存储了 block 指针 block_n,此时这两个指针还是普通的指针。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615100947634-2140254844.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615100947634-2140254844.jpg" alt="">
</a></p>
<p>当文件系统 /dev/cdrom 挂载到 /mnt 上后,/mnt 此时就已经成为另一个文件系统的入口了,因此它需要连接两边文件系统的 inode 和 data block。但是如何连接呢?如下图。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615101016571-14202340.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615101016571-14202340.jpg" alt="">
</a></p>
<p>在根文件系统的 inode table 中,为 /mnt 重新分配一个 inode 记录 m,该记录的 block 指针 block_m 指向文件系统 /dev/cdrom 中的 data block。既然为 /mnt 分配了新的 inode 记录 m,那么在”/“目录的 data block 中,也需要修改其 inode 指针为 inode_m 以指向 m 记录。同时,原来 inode table 中的 inode 记录 n 就被标记为暂时不可用。</p>
<p>block_m 指向的是文件系统 /dev/cdrom 的 data block,所以严格说起来,除了 /mnt 的元数据信息即 inode 记录 m 还在根文件系统上,/mnt 的 data block 已经是在 /dev/cdrom 中的了。这就是挂载新文件系统后实现的跨文件系统,它将挂载点的元数据信息和数据信息分别存储在不同的文件系统上。</p>
<p>挂载完成后,将在 /proc/self/{mounts,mountstats,mountinfo} 这三个文件中写入挂载记录和相关的挂载信息,并会将 /proc/self/mounts 中的信息同步到 /etc/mtab 文件中,当然,如果挂载时加了 - n 参数,将不会同步到 /etc/mtab。</p>
<p>而卸载文件系统,其实质是移除临时新建的 inode 记录 (当然,在移除前会检查是否正在使用) 及其指针,并将指针指回原来的 inode 记录,这样 inode 记录中的 block 指针也就同时生效而找回对应的 data block 了。由于卸载只是移除 inode 记录,所以使用挂载点和文件系统都可以实现卸载,因为它们是联系在一起的。</p>
<p>下面是分析或结论。</p>
<p>(1). 挂载点挂载时的 inode 记录是新分配的。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span># 挂载前挂载点/mnt的inode号
</span></span><span style="display:flex;"><span>[root@server2 tmp]# ll -id /mnt
</span></span><span style="display:flex;"><span>100663447 drwxr-xr-x. 2 root root 6 Aug 12 2015 /mnt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[root@server2 tmp]# mount /dev/cdrom /mnt
</span></span><span style="display:flex;"><span># 挂载后挂载点的inode号
</span></span><span style="display:flex;"><span>[root@server2 tmp]# ll -id /mnt
</span></span><span style="display:flex;"><span>1856 dr-xr-xr-x 8 root root 2048 Dec 10 2015 mnt
</span></span></code></pre></div><p>由此可以验证,inode 号确实是重新分配的。</p>
<p>(2). 挂载后,挂载点的内容将暂时不可见、不可用,卸载后文件又再次可见、可用。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span># 在挂载前,向挂载点中创建几个文件
</span></span><span style="display:flex;"><span>[root@server2 tmp]# touch /mnt/a.txt
</span></span><span style="display:flex;"><span>[root@server2 tmp]# mkdir /mnt/abcdir
</span></span><span style="display:flex;"><span># 挂载
</span></span><span style="display:flex;"><span>[root@server2 tmp]# mount /dev/cdrom /mnt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># 挂载后,挂载点中将找不到刚创建的文件
</span></span><span style="display:flex;"><span>[root@server2 tmp]# ll /mnt
</span></span><span style="display:flex;"><span>total 636
</span></span><span style="display:flex;"><span>-r--r--r-- 1 root root 14 Dec 10 2015 CentOS_BuildTag
</span></span><span style="display:flex;"><span>dr-xr-xr-x 3 root root 2048 Dec 10 2015 EFI
</span></span><span style="display:flex;"><span>-r--r--r-- 1 root root 215 Dec 10 2015 EULA
</span></span><span style="display:flex;"><span>-r--r--r-- 1 root root 18009 Dec 10 2015 GPL
</span></span><span style="display:flex;"><span>dr-xr-xr-x 3 root root 2048 Dec 10 2015 images
</span></span><span style="display:flex;"><span>dr-xr-xr-x 2 root root 2048 Dec 10 2015 isolinux
</span></span><span style="display:flex;"><span>dr-xr-xr-x 2 root root 2048 Dec 10 2015 LiveOS
</span></span><span style="display:flex;"><span>dr-xr-xr-x 2 root root 612352 Dec 10 2015 Packages
</span></span><span style="display:flex;"><span>dr-xr-xr-x 2 root root 4096 Dec 10 2015 repodata
</span></span><span style="display:flex;"><span>-r--r--r-- 1 root root 1690 Dec 10 2015 RPM-GPG-KEY-CentOS-7
</span></span><span style="display:flex;"><span>-r--r--r-- 1 root root 1690 Dec 10 2015 RPM-GPG-KEY-CentOS-Testing-7
</span></span><span style="display:flex;"><span>-r--r--r-- 1 root root 2883 Dec 10 2015 TRANS.TBL
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># 卸载后,挂载点/mnt中的文件将再次可见
</span></span><span style="display:flex;"><span>[root@server2 tmp]# umount /mnt
</span></span><span style="display:flex;"><span>[root@server2 tmp]# ll /mnt
</span></span><span style="display:flex;"><span>total 0
</span></span><span style="display:flex;"><span>drwxr-xr-x 2 root root 6 Jun 9 08:18 abcdir
</span></span><span style="display:flex;"><span>-rw-r--r-- 1 root root 0 Jun 9 08:18 a.txt
</span></span></code></pre></div><p>之所以会这样,是因为挂载文件系统后,挂载点原来的 inode 记录暂时被标记为不可用,关键是没有指向该 inode 记录的 inode 指针了。在卸载文件系统后,又重新启用挂载点原来的 inode 记录,”/“目录下的 mnt 的 inode 指针又重新指向该 inode 记录。</p>
<p>(3). 挂载后,挂载点的元数据和 data block 是分别存放在不同文件系统上的。</p>
<p>(4). 挂载点即使在挂载后,也还是属于源文件系统的文件。</p>
<h2 id="多文件系统操作关联-多文件系统操作关联多文件系统操作关联"><a href="#%E5%A4%9A%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%93%8D%E4%BD%9C%E5%85%B3%E8%81%94" title="多文件系统操作关联"></a>多文件系统操作关联<a class="td-heading-self-link" href="#%e5%a4%9a%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e6%93%8d%e4%bd%9c%e5%85%b3%e8%81%94-%e5%a4%9a%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e6%93%8d%e4%bd%9c%e5%85%b3%e8%81%94%e5%a4%9a%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e6%93%8d%e4%bd%9c%e5%85%b3%e8%81%94" aria-label="Heading self-link"></a></h2>
<p>假如下图中的圆代表一块硬盘,其中划分了 3 个区即 3 个文件系统。其中根是根文件系统,/mnt 是另一个文件系统 A 的入口,A 文件系统挂载在 /mnt 上,/mnt/cdrom 也是一个文件系统 B 的入口,B 文件系统挂载在 /mnt/cdrom 上。每个文件系统都维护了一些 inode table,这里假设图中的 inode table 是每个文件系统所有块组中的 inode table 的集合表。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615101444509-1613755325.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615101444509-1613755325.jpg" alt="">
</a></p>
<p>如何读取 /var/log/messages 呢?这是和”/“在同一个文件系统的文件读取,在前面单文件系统中已经详细说明了。</p>
<p>但如何读取 A 文件系统中的 /mnt/a.log 呢?首先,从根文件系统找到 /mnt 的 inode 记录,这是单文件系统内的查找;然后根据此 inode 记录的 block 指针,定位到 /mnt 的 data block 中,这些 block 是 A 文件系统的 data block;然后从 /mnt 的 data block 中读取 a.log 记录,并根据 a.log 的 inode 指针定位到 A 文件系统的 inode table 中对应 a.log 的 inode 记录;最后从此 inode 记录的 block 指针找到 a.log 的 data block。至此,就能读取到 /mnt/a.log 文件的内容。</p>
<p>下图能更完整的描述上述过程。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615101506743-1757840484.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615101506743-1757840484.jpg" alt="">
</a></p>
<p>那么又如何读取 /mnt/cdrom 中的 /mnt/cdrom/a.rpm 呢?这里 cdrom 代表的文件系统 B 挂载点位于 /mnt 下,所以又多了一个步骤。先找到”/“,再找到根中的 mnt,进入到 mnt 文件系统中,找到 cdrom 的 data block,再进入到 cdrom 找到 a.rpm。也就是说,mnt 目录文件存放位置是根,cdrom 目录文件存放位置是 mnt,最后 a.rpm 存放的位置才是 cdrom。</p>
<p>继续完善上图。如下。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615101532431-1952370802.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615101532431-1952370802.jpg" alt="">
</a></p>
<p>相比 ext2 文件系统,ext3 多了一个日志功能。</p>
<p>在 ext2 文件系统中,只有两个区:数据区和元数据区。如果正在向 data block 中填充数据时突然断电,那么下一次启动时就会检查文件系统中数据和状态的一致性,这段检查和修复可能会消耗大量时间,甚至检查后无法修复。之所以会这样是因为文件系统在突然断电后,它不知道上次正在存储的文件的 block 从哪里开始、哪里结束,所以它会扫描整个文件系统进行排除 (也许是这样检查的吧)。</p>
<p>而在创建 ext3 文件系统时会划分三个区:数据区、日志区和元数据区。每次存储数据时,先在日志区中进行 ext2 中元数据区的活动,直到文件存储完成后标记上 commit 才将日志区中的数据转存到元数据区。当存储文件时突然断电,下一次检查修复文件系统时,只需要检查日志区的记录,将 bmap 对应的 data block 标记为未使用,并把 inode 号标记未使用,这样就不需要扫描整个文件系统而耗费大量时间。</p>
<p>虽说 ext3 相比 ext2 多了一个日志区转写元数据区的动作而导致 ext3 相比 ext2 性能要差一点,特别是写众多小文件时。但是由于 ext3 其他方面的优化使得 ext3 和 ext2 性能几乎没有差距。</p>
<p>回顾前面关于 ext2 和 ext3 文件系统的存储格式,它使用 block 为存储单元,每个 block 使用 bmap 中的位来标记是否空闲,尽管使用划分块组的方法优化提高了效率,但是一个块组内部仍然使用 bmap 来标记该块组内的 block。对于一个巨大的文件,扫描整个 bmap 都将是一件浩大的工程。另外在 inode 寻址方面,ext2/3 使用直接和间接的寻址方式,对于三级间接指针,可能要遍历的指针数量是非常非常巨大的。</p>
<p>ext4 文件系统的最大特点是在 ext3 的基础上使用区 (extent,或称为段) 的概念来管理。一个 extent 尽可能的包含物理上连续的一堆 block。inode 寻址方面也一样使用区段树的方式进行了改进。</p>
<p>默认情况下,EXT4 不再使用 EXT3 的 block mapping 分配方式 ,而改为 Extent 方式分配。</p>
<p>以下是 ext4 文件系统中一个文件的 inode 属性示例,注意最后两行的 EXTENTS。</p>
<div class="highlight"><pre tabindex="0" style="background-color:#f8f8f8;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>Inode: 12 Type: regular Mode: 0644 Flags: 0x80000
</span></span><span style="display:flex;"><span>Generation: 476513974 Version: 0x00000000:00000001
</span></span><span style="display:flex;"><span>User: 0 Group: 0 Size: 11
</span></span><span style="display:flex;"><span>File ACL: 0 Directory ACL: 0
</span></span><span style="display:flex;"><span>Links: 1 Blockcount: 8
</span></span><span style="display:flex;"><span>Fragment: Address: 0 Number: 0 Size: 0
</span></span><span style="display:flex;"><span> ctime: 0x5b628ca0:491d6224 -- Thu Aug 2 12:46:24 2018
</span></span><span style="display:flex;"><span> atime: 0x5b628ca0:491d6224 -- Thu Aug 2 12:46:24 2018
</span></span><span style="display:flex;"><span> mtime: 0x5b628ca0:491d6224 -- Thu Aug 2 12:46:24 2018
</span></span><span style="display:flex;"><span>crtime: 0x5b628ca0:491d6224 -- Thu Aug 2 12:46:24 2018
</span></span><span style="display:flex;"><span>Size of extra inode fields: 28
</span></span><span style="display:flex;"><span>EXTENTS:
</span></span><span style="display:flex;"><span>(0):33409
</span></span></code></pre></div><p>(1). 关于 EXT4 的结构特征</p>
<p>EXT4 在总体结构上与 EXT3 相似,大的分配方向都是基于相同大小的块组,每个块组内分配固定数量的 inode、可能的 superblock (或备份) 及 GDT。</p>
<p>EXT4 的 inode 结构做了重大改变,为增加新的信息,大小由 EXT3 的 128 字节增加到默认的 256 字节,同时 inode 寻址索引不再使用 EXT3 的”12 个直接寻址块 + 1 个一级间接寻址块 + 1 个二级间接寻址块 + 1 个三级间接寻址块” 的索引模式,而改为 4 个 Extent 片断流,每个片断流设定片断的起始 block 号及连续的 block 数量 (有可能直接指向数据区,也有可能指向索引块区)。</p>
<p>片段流即下图中索引节点 (index node block) 部分的绿色区域,每个 15 字节,共 60 字节。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615101630493-1536814900.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615101630493-1536814900.jpg" alt="">
</a></p>
<p>(2).EXT4 删除数据的结构更改。</p>
<p>EXT4 删除数据后,会依次释放文件系统 bitmap 空间位、更新目录结构、释放 inode 空间位。</p>
<p>(3).ext4 使用多 block 分配方式。</p>
<p>在存储数据时,ext3 中的 block 分配器一次只能分配 4KB 大小的 Block 数量,而且每存储一个 block 前就标记一次 bmap。假如存储 1G 的文件,blocksize 是 4KB,那么每存储完一个 Block 就将调用一次 block 分配器,即调用的次数为 1024*1024/4KB=262144 次,标记 bmap 的次数也为 1024*1024/4=262144 次。</p>
<p>而在 ext4 中根据区段来分配,可以实现调用一次 block 分配器就分配一堆连续的 block,并在存储这一堆 block 前一次性标记对应的 bmap。这对于大文件来说极大的提升了存储效率。</p>
<p>最大的缺点是它在创建文件系统的时候就划分好一切需要划分的东西,以后用到的时候可以直接进行分配,也就是说它不支持动态划分和动态分配。对于较小的分区来说速度还好,但是对于一个超大的磁盘,速度是极慢极慢的。例如将一个几十 T 的磁盘阵列格式化为 ext4 文件系统,可能你会因此而失去一切耐心。</p>
<p>除了格式化速度超慢以外,ext4 文件系统还是非常可取的。当然,不同公司开发的文件系统都各有特色,最主要的还是根据需求选择合适的文件系统类型。</p>
<p>每一个分区格式化后都可以建立一个文件系统,Linux 上可以识别很多种文件系统,那么它是如何识别的呢?另外,在我们操作分区中的文件时,并没有指定过它是哪个文件系统的,各种不同的文件系统如何被我们用户以无差别的方式操作呢?这就是虚拟文件系统的作用。</p>
<p>虚拟文件系统为用户操作各种文件系统提供了通用接口,使得用户执行程序时不需要考虑文件是在哪种类型的文件系统上,应该使用什么样的系统调用来操作该文件。有了虚拟文件系统,只要将所有需要执行的程序调用 VFS 的系统调用就可以了,剩下的动作由 VFS 来帮忙完成。</p>
<p><a href="https://www.junmajinlong.com/img/linux/733013-20170615101808150-916356306.jpg"><img src="https://www.junmajinlong.com/img/linux/733013-20170615101808150-916356306.jpg" alt="">
</a></p></description></item><item><title>Blog: CPU 是如何读写内存的?</title><link>https://desistdaydream.github.io/blog/copy/%E5%85%AC%E4%BC%97%E5%8F%B7%E7%A0%81%E5%86%9C%E7%9A%84%E8%8D%92%E5%B2%9B%E6%B1%82%E7%94%9F-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E8%AF%9D%E9%A2%98%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0/CPU-%E6%98%AF%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99%E5%86%85%E5%AD%98%E7%9A%84/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://desistdaydream.github.io/blog/copy/%E5%85%AC%E4%BC%97%E5%8F%B7%E7%A0%81%E5%86%9C%E7%9A%84%E8%8D%92%E5%B2%9B%E6%B1%82%E7%94%9F-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E8%AF%9D%E9%A2%98%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0/CPU-%E6%98%AF%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99%E5%86%85%E5%AD%98%E7%9A%84/</guid><description>
<p><a href="https://mp.weixin.qq.com/s/S3Cn6KsDGKqxxP58y2m67Q">https://mp.weixin.qq.com/s/S3Cn6KsDGKqxxP58y2m67Q</a></p>
<p>如果你觉得这是一个非常简单的问题,那么你真应该好好读读本文,<strong>我敢保证这个问题绝没有你想象的那么简单</strong>。注意,一定要完本文,<strong>否则可能会得出错误的结论</strong>。闲话少说,让我们来看看 CPU 在读写内存时底层究竟发生了什么。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494695-3520afa8-7698-4bb7-b054-63d8593e6183.png" alt="">
<strong>谁来告诉 CPU 读写内存</strong>
我们第一个要搞清楚的问题是:谁来告诉 CPU 去读写内存?答案很明显,是程序员,更具体的是编译器。CPU 只是按照指令按部就班的执行,机器指令从哪里来的呢?是编译器生成的,程序员通过高级语言编写程序,编译器将其翻译为机器指令,机器指令来告诉 CPU 去读写内存。在精简指令集架构下会有特定的机器指令,Load/Store 指令来读写内存,以 x86 为代表的复杂指令集架构下没有特定的访存指令。精简指令集下,一条机器指令操作的数据必须来存放在寄存器中,不能直接操作内存数据,因此 RISC 下,数据必须先从内存搬运到寄存器,这就是为什么 RISC 下会有特定的 Load/Store 访存指令,明白了吧。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494595-c823fd37-ed5d-4ac8-bbb8-1a31ed3045a6.gif" alt="">而 x86 下无此限制,一条机器指令操作的数据可以来自于寄存器也可以来自内存,因此这样一条机器指令在执行过程中会首先从内存中读取数据。关于复杂指令集以及精简指令集你可以参考这两篇文章《<a href="http://mp.weixin.qq.com/s?__biz=Mzg4OTYzODM4Mw==&amp;mid=2247485740&amp;idx=1&amp;sn=5e21003aa245c64516225cbdec30fc25&amp;chksm=cfe995acf89e1cbaf208a25999d6c9b08e505ea8b8a49564e08c63a819b0a4fa1160893a8047&amp;scene=21#wechat_redirect">CPU 进化论:复杂指令集</a>》与《<a href="http://mp.weixin.qq.com/s?__biz=Mzg4OTYzODM4Mw==&amp;mid=2247485741&amp;idx=1&amp;sn=45afcce8e8e8ec198a9b09c32c1e6aa8&amp;chksm=cfe995adf89e1cbb833ca61741028bee6ccfeb1e928efe60a3a8fcf1fa01da3df4ef49a063de&amp;scene=21#wechat_redirect">不懂精简指令集还敢说自己是程序员?</a>》</p>
<h5 id="两种内存读写"><strong>两种内存读写</strong><a class="td-heading-self-link" href="#%e4%b8%a4%e7%a7%8d%e5%86%85%e5%ad%98%e8%af%bb%e5%86%99" aria-label="Heading self-link"></a></h5>
<p>现在我们知道了,是特定的机器指令告诉 CPU 要去访问内存。不过,值得注意的是,不管是 RISC 下特定的 Load/Store 指令还是 x86 下包含在一条指令内部的访存操作,这里读写的都是内存中的数据,除此之外还要意识到,CPU 除了从内存中读写数据外,还要从内存中读取下一条要执行的机器指令。毕竟,我们的计算设备都遵从冯诺依曼架构:<strong>程序和数据一视同仁,都可以存放在内存中</strong>。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494722-62b1ce8b-3e11-48ac-bc69-66d8c2d7cf28.png" alt="">现在,我们清楚了 CPU 读写内存其实是由两个因素来驱动的:</p>
<ol>
<li>
<p>程序执行过程中需要读写来自内存中的数据</p>
</li>
<li>
<p>CPU 需要访问内存读取下一条要执行的机器指令</p>
</li>
</ol>
<p>然后 CPU 根据机器指令中包含的内存地址或者 PC 寄存器中下一条机器指令的地址访问内存。这不就完了吗?有了内存地址,CPU 利用硬件通路直接读内存就好了,你可能也是这样的想的。真的是这样吗?别着急,我们接着往下看,这两节只是开胃菜,正餐才刚刚开始。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494648-fe04d9ee-fbc6-4b6f-b939-b7c4f24dbe63.webp" alt=""></p>
<h5 id="急性子吃货-vs-慢性子厨师"><strong>急性子吃货 VS 慢性子厨师</strong><a class="td-heading-self-link" href="#%e6%80%a5%e6%80%a7%e5%ad%90%e5%90%83%e8%b4%a7-vs-%e6%85%a2%e6%80%a7%e5%ad%90%e5%8e%a8%e5%b8%88" aria-label="Heading self-link"></a></h5>
<p>假设你是一个整天无所事事的吃货,整天无所事事,唯一的爱好就是找一家餐厅吃吃喝喝,由于你是职业吃货,因此吃起来非常职业,1 分钟就能吃完一道菜,但这里的厨师就没有那么职业了,炒一道菜速度非常慢,大概需要 1 小时 40 分钟才能炒出一道菜,速度比你慢了 100 倍,如果你是这个吃货,大概率会疯掉的。而 CPU 恰好就是这样一个吃货,内存就是这样一个慢吞吞的厨师,而且随着时间的推移这两者的速度差异正在越来越大:<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494714-c2e50753-c4e8-43d1-afa6-3c923c6807d2.webp" alt="">在这种速度差异下,CPU 执行一条涉及内存读写指令时需要等**“很长一段时间“<strong>数据才能</strong>”缓缓的“<strong>从内存读取到 CPU 中,在这种情况</strong>你还认为 CPU 应该直接读写内存吗**?</p>
<h5 id="无处不在的-28-定律"><strong>无处不在的 28 定律</strong><a class="td-heading-self-link" href="#%e6%97%a0%e5%a4%84%e4%b8%8d%e5%9c%a8%e7%9a%84-28-%e5%ae%9a%e5%be%8b" aria-label="Heading self-link"></a></h5>
<p>28 定律我想就不用多介绍了吧,在《<a href="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494774-8e88aec6-cda9-4df8-bc4a-c6b0721656b0.png">不懂精简指令集还敢说自己是程序员</a>集中起来然后呢?放到哪里呢?当然是放到一种比内存速度更快的存储介质上,这种介质就是我们熟悉的 SRAM,普通内存一般是 DRAM,这种读写速度更快的介质充当 CPU 和内存之间的 Cache,这就是所谓的缓存。</p>
<h5 id="四两拨千斤"><strong>四两拨千斤</strong><a class="td-heading-self-link" href="#%e5%9b%9b%e4%b8%a4%e6%8b%a8%e5%8d%83%e6%96%a4" aria-label="Heading self-link"></a></h5>
<p>我们把经常用到的数据放到 cache 中存储,CPU 访问内存时首先查找 cache,如果能找到,也就是命中,那么就赚到了,直接返回即可,找不到再去查找内存并更新 cache。我们可以看到,<strong>有了 cache,CPU 不再直接与内存打交道了</strong>。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494726-1c190d71-806e-4781-bce8-ae233a4881b9.png" alt="">但 cache 的快速读写能力是有代价的,代价就是 Money,造价不菲,<strong>因此我们不能把内存完全替换成 cache 的 SRAM,那样的计算机你我都是买不起的</strong>。因此 cache 的容量不会很大,但由于程序局部性原理,<strong>因此很小的 cache 也能有很高的命中率</strong>,从而带来性能的极大提升,有个词叫<strong>四两拨千斤</strong>,用到 cache 这里再合适不过。</p>
<h5 id="天下没有免费的午餐"><strong>天下没有免费的午餐</strong><a class="td-heading-self-link" href="#%e5%a4%a9%e4%b8%8b%e6%b2%a1%e6%9c%89%e5%85%8d%e8%b4%b9%e7%9a%84%e5%8d%88%e9%a4%90" aria-label="Heading self-link"></a></h5>
<p>虽然小小的 cache 能带来性能的极大提升,但,这也是有代价的。这个代价出现在写内存时。当 CPU 需要写内存时该怎么办呢?现在有了 cache,CPU 不再直接与内存打交道,因此 CPU 直接写 cache,但此时就会有一个问题,那就是 cache 中的值更新了,但内存中的值还是旧的,这就是所谓的不一致问题,inconsistent.就像下图这样,cache 中变量的值是 4,但内存中的值是 2。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494764-74ebde75-ea0b-4c97-9d10-32cf9cdd2be4.png" alt=""></p>
<h5 id="同步缓存更新"><strong>同步缓存更新</strong><a class="td-heading-self-link" href="#%e5%90%8c%e6%ad%a5%e7%bc%93%e5%ad%98%e6%9b%b4%e6%96%b0" aria-label="Heading self-link"></a></h5>
<p>常用 redis 的同学应该很熟悉这个问题,<strong>可是你知道吗?这个问题早就在你读这篇文章用的计算设备其包含的 CPU 中已经遇到并已经解决了。<strong>最简单的方法是这样的,当我们更新 cache 时一并把内存也更新了,这种方法被称为 write-through,很形象吧。可是如果当 CPU 写 cache 时,cache 中没有相应的内存数据该怎么呢?这就有点麻烦了,首先我们需要把该数据从内存加载到 cache 中,然后更新 cache,再然后更新内存。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494833-7d54bed1-0b2b-4dec-925f-f816342ede57.png" alt="">这种实现方法虽然简单,但有一个问题,那就是性能问题,在这种方案下</strong>写内存就不得不访问内存</strong>,上文也提到过 CPU 和内存可是有很大的速度差异哦,因此这种方案性能比较差。有办法解决吗?答案是肯定的。</p>
<h5 id="异步更新缓存"><strong>异步更新缓存</strong><a class="td-heading-self-link" href="#%e5%bc%82%e6%ad%a5%e6%9b%b4%e6%96%b0%e7%bc%93%e5%ad%98" aria-label="Heading self-link"></a></h5>
<p>这种方法性能差不是因为写内存慢,写内存确实是慢,更重要的原因是 CPU 在同步等待,因此很自然的,这类问题的统一解法就是把同步改为异步。关于同步和异步的话题,你可以参考这篇文章《<a href="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494782-5d7755ab-d9a8-428d-b1ad-3f898aa5cdc4.png">从小白到高手,你需要理解同步和异步</a>现在你应该能看到,添加 cache 后会带来一系列问题,更不用说 cache 的替换算法,毕竟 cache 的容量有限,当 cache 已满时,增加一项新的数据就要剔除一项旧的数据,那么该剔除谁就是一个非常关键的问题,限于篇幅就不在这里详细讲述了,你可以参考《深入理解操作系统》第 7 章有关于该策略的讲解。</p>
<h5 id="多级-cache"><strong>多级 cache</strong><a class="td-heading-self-link" href="#%e5%a4%9a%e7%ba%a7-cache" aria-label="Heading self-link"></a></h5>
<p>现代 CPU 为了增加 CPU 读写内存性能,已经在 CPU 和内存之间增加了多级 cache,典型的有三级,L1、L2 和 L3,CPU 读内存时首先从 L1 cache 找起,能找到直接返回,否则就要在 L2 cache 中找,L2 cache 中找不到就要到 L3 cache 中找,还找不到就不得不访问内存了。因此我们可以看到,<strong>现代计算机系统 CPU 和内存之间其实是有一个 cache 的层级结构的</strong>。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494897-db05b255-a5ca-42e7-beb9-e4b976167d71.png" alt="">你以为这就完了吗?哈哈,哪有这么容易的,否则也不会是终面题目了。那么当 CPU 读写内存时除了面临上述问题外还需要处理哪些问题呢?</p>
<h5 id="多核多问题"><strong>多核,多问题</strong><a class="td-heading-self-link" href="#%e5%a4%9a%e6%a0%b8%e5%a4%9a%e9%97%ae%e9%a2%98" aria-label="Heading self-link"></a></h5>
<p>当摩尔定律渐渐失效后鸡贼的人类换了另一种提高 CPU 性能的方法,既然单个 CPU 性能不好提升了,我们还可以堆数量啊,这样,CPU 进入多核时代,程序员开始进入苦逼时代。拥有一堆核心的 CPU 其实是没什么用的,<strong>关键需要有配套的多线程程序才能真正发挥多核的威力</strong>,但写过多线程程序的程序员都知道,能写出来不容易,能写出来并且能正确运行更不容易,关于多线程与多线程编程的详细阐述请参见《深入理解操作系统》第 5、6 两章(关注公众号“码农的荒岛求生”并回复“操作系统”)。CPU 开始拥有多个核心后不但苦逼了软件工程师,硬件工程师也不能幸免。前文提到过,为提高 CPU 访存性能,CPU 和内存之间会有一个层 cache,但当 CPU 有多个核心后新的问题来了:<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494753-a8075ddc-1b20-4c77-ad15-d71c3d78ac07.png" alt="">
看出问题在哪里了吗?一个初始值为 2 的变量,在分别+2 和+4 后正确的结果应该是 2+2+4 = 8,但从上图可以看出<strong>内存中 X 的值却为 6</strong>,问题出在哪了呢?</p>
<h5 id="多核-cache-一致性"><strong>多核 cache 一致性</strong><a class="td-heading-self-link" href="#%e5%a4%9a%e6%a0%b8-cache-%e4%b8%80%e8%87%b4%e6%80%a7" aria-label="Heading self-link"></a></h5>
<p>有的同学可能已经发现了,问题出在了内存中一个 X 变量<strong>在 C1 和 C2 的 cache 中有共计两个副本,当 C1 更新 cache 时没有同步修改 C2 cache 中 X 的值</strong>。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494851-f1847fc5-1967-4cf7-9c17-f589ca806aaf.png" alt="">解决方法是什么呢?显然,如果一个 cache 中待更新的变量同样存在于其它核心的 cache,那么你需要一并将其它 cache 也更新好。现在你应该看到,CPU 更新变量时不再简单的只关心自己的 cache 和内存,<strong>你还需要知道这个变量是不是同样存在于其它核心中的 cache</strong>,如果存在需要一并更新。当然,这还只是简单的读,写就更加复杂了,实际上,现代 CPU 中有一套协议来专门维护缓存的一致性,比较经典的包括 MESI 协议等。为什么程序员需要关心这个问题呢?原因很简单,<strong>你最好写出对 cache 一致性协议友好的程序</strong>,<strong>因为 cache 频繁维护一致性也是有性能代价的</strong>。同样的,限于篇幅,这个话题不再详细阐述,该主题同样值得单独成篇,敬请期待。</p>
<h5 id="够复杂了吧"><strong>够复杂了吧!</strong><a class="td-heading-self-link" href="#%e5%a4%9f%e5%a4%8d%e6%9d%82%e4%ba%86%e5%90%a7" aria-label="Heading self-link"></a></h5>
<p>怎么样?到目前为止,是不是 CPU 读写内存没有看上去那么简单?现代计算机中 CPU 和内存之间有多级 cache,<strong>CPU 读写内存时不但要维护 cache 和内存的一致性,同样需要维护多核间 cache 的一致性</strong>。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494804-e27375bb-5047-4f46-aefe-0f5667fddf60.webp" alt="">你以为这就完了,NONO,最大的谜团其实是接下来要讲的。</p>
<h5 id="你以为的不是你以为的"><strong>你以为的不是你以为的</strong><a class="td-heading-self-link" href="#%e4%bd%a0%e4%bb%a5%e4%b8%ba%e7%9a%84%e4%b8%8d%e6%98%af%e4%bd%a0%e4%bb%a5%e4%b8%ba%e7%9a%84" aria-label="Heading self-link"></a></h5>
<p>现代程序员写程序基本上不需要关心<strong>内存是不是足够这个问题</strong>,但这个问题在远古时代绝对是困扰程序员的一大难题。如果你去想一想,其实现代计算机内存也没有足够大的让我们随便申请的地步,<strong>但是你在写程序时是不是基本上没有考虑过内存不足该怎么办?<strong>为什么我们在内存资源依然处于匮乏的现代可以做到申请内存时却进入内存极大丰富的共产主义理想社会了呢?原来这背后的功臣是我们熟悉的</strong>操作系统</strong>。操作系统对每个进程都维护一个假象,即,每个进程独占系统内存资源;同时给程序员一个承诺,让程序员可以认为在写程序时有一大块连续的内存可以使用。这当然是不可能不现实的,因此操作系统给进程的地址空间必然不是真的,但我们又不好将其称之为“<strong>假的地址空间</strong>”,这会让人误以为计算机科学界里骗子横行,因此就换了一个好听的名字,<strong>虚拟内存</strong>,一个“<strong>假的地址空间</strong>”更高级的叫法。<strong>进程其实一直活在操作系统精心维护的幻觉当中</strong>,就像《盗梦空间》一样,关于虚拟内存的详尽阐述请参见《深入理解操作系统》第七章(关注公众号“码农的荒岛求生”并回复“操作系统”)。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494790-0d313ab7-571e-40a4-8135-4915c01d9d6f.webp" alt="">从这个角度看,其实最擅长包装的是计算机科学界,哦,对了,他们不但擅长包装还擅长抽象。</p>
<h5 id="天真的-cpu"><strong>天真的 CPU</strong><a class="td-heading-self-link" href="#%e5%a4%a9%e7%9c%9f%e7%9a%84-cpu" aria-label="Heading self-link"></a></h5>
<p>CPU 真的是很傻很天真的存在。上一节讲的操作系统施加的障眼法把 CPU 也蒙在鼓里。CPU 执行机器指令时,指令指示 CPU 从内存地址 A 中取出数据,然后 CPU 执行机器指令时下发命令:“给我从地址 A 中取出数据”,尽管真的能从地址 A 中取出数据,但这个地址 A 不是真的,不是真的,不是真的。因为这个地址 A 属于虚拟内存,也就是那个“假的地址空间”,现代 CPU 内部有一个叫做 MMU 的模块将这假的地址 A 转换为真的地址 B,将地址 A 转换为真实的地址 B 之后才是本文之前讲述的关于 cache 的那一部分。<img src="https://notes-learning.oss-cn-beijing.aliyuncs.com/ogei92/1625466494784-5941f257-6712-42f8-9a22-444bf53854d1.png" alt="">你以为这终于应该讲完了吧!NONO!CPU 给出内存地址,此后该地址被转为真正的物理内存地址,接下来查 L1 cache,L1 cache 不命中查 L2 cache,L2 cache 不命中查 L3 cache,L3 cache 不能命中查内存。各单位注意,各单位注意,到查内存时还不算完,现在有了虚拟内存,<strong>内存其实也是一层 cache,是磁盘的 cache,也就是说查内存也有可能不会命中</strong>,因为内存中的数据可能被虚拟内存系统放到磁盘中了,<strong>如果内存也不能命中就要查磁盘</strong>。So crazy,限于篇幅这个过程不再展开,《深入理解操作系统》第七章有完整的讲述。至此,CPU 读写内存时完整的过程阐述完毕。</p>
<h5 id="总结"><strong>总结</strong><a class="td-heading-self-link" href="#%e6%80%bb%e7%bb%93" aria-label="Heading self-link"></a></h5>
<p>现在你还认为 CPU 读写内存非常简单吗?这一过程涉及到的硬件以及硬件逻辑包括:L1 cache、L2 cache、L3 cache、多核缓存一致性协议、MMU、内存、磁盘;软件主要包括操作系统。<strong>这一看似简单的操作涉及几乎所有计算机系统中的核心组件,需要软件以及硬件密切配合才能完成</strong>。这个过程给程序员的启示是:1),现代计算机系统是非常复杂的;2),<strong>你需要写出对 cache 友好的程序</strong>。我是小风哥,希望这篇文章对大家理解 CPU 以及内存读写有所帮助。***参考资料***<strong>1,《深入理解操作系统》第七章</strong>,<strong>关注公众号“码农的荒岛求生”并回复“操作系统”即可阅读</strong>。<a href="http://mp.weixin.qq.com/s?__biz=Mzg4OTYzODM4Mw==&amp;mid=2247485740&amp;idx=1&amp;sn=5e21003aa245c64516225cbdec30fc25&amp;chksm=cfe995acf89e1cbaf208a25999d6c9b08e505ea8b8a49564e08c63a819b0a4fa1160893a8047&amp;scene=21#wechat_redirect"><strong>2,CPU 进化论:复杂指令集的诞生</strong></a><a href="http://mp.weixin.qq.com/s?__biz=Mzg4OTYzODM4Mw==&amp;mid=2247485741&amp;idx=1&amp;sn=45afcce8e8e8ec198a9b09c32c1e6aa8&amp;chksm=cfe995adf89e1cbb833ca61741028bee6ccfeb1e928efe60a3a8fcf1fa01da3df4ef49a063de&amp;scene=21#wechat_redirect">**3,不懂精简指令集还敢说自己是程序员?**</a></p></description></item><item><title>Blog: Linux Torvalds 采访</title><link>https://desistdaydream.github.io/blog/copy/mVo3S_F0RoxCToawrTCnlA/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://desistdaydream.github.io/blog/copy/mVo3S_F0RoxCToawrTCnlA/</guid><description>
<p><a href="https://mp.weixin.qq.com/s/mVo3S_F0RoxCToawrTCnlA">https://mp.weixin.qq.com/s/mVo3S_F0RoxCToawrTCnlA</a></p>
<p>作者丨 Jeremy Andrews</p>
<p>译者丨屠灵</p>
<p>策划丨蔡芳芳</p>
<p>Linux 诞生于 1991 年,距今已经 30 年了。虽然它一开始只是 Linus 的一个个人项目,而非出于要开发一个新操作系统的伟大梦想,但如今的 Linux 早已无处不在。</p>
<p>30 年前,当 Linus Torvalds 第一次发布 Linux 内核时,他还是赫尔辛基大学的一名 21 岁的学生。他宣布说:“我正在开发一个(免费的)操作系统(这只是个爱好,不会做得很大,也不会很专业……)”。30 年后,500 强超级计算机和 70% 以上的智能手机都在运行 Linux。很显然,Linux 不仅大,而且很专业。</p>
<p>30 年来,Linus Torvalds 一直在领导着 Linux 内核的开发,启发了无数开发者和开源项目。2005 年,Linus 开发了 Git,用来管理内核开发过程。Git 现在已经成为最流行的版本控制系统,受到无数开源和私有项目的信任。</p>
<p>正值 Linux 诞生 30 周年之际,Linus Torvalds 通过电子邮件回复了 Tag 1 咨询公司的创始合伙人 / 首席执行官 Jeremy Andrews 的访谈问题(《An Interview With Linus Torvalds: Linux and Git - Part 1》),回顾并总结了过去这些年他在领导大型开源项目过程中得到的真知灼见。本文着重介绍 Linux 内核开发和 Git。InfoQ 对访谈内容进行了翻译,以飨读者。</p>
<p>Linux 内核开发</p>
<p><strong>Jeremy Andrews:Linux 无处不在,它是整个开源世界的灵感源泉。当然,事情并不是从一开始就这样的。1991 年,你在 comp.os.minix Usenet 新闻组中发布了一个 Linux 内核。十年后,你写了一本书,叫作“Just for Fun: The Story of an Accidental Revolutionary”(中译名:《只是为了好玩:Linux 之父林纳斯自传》),对那段历史进行了深度回顾。今年 8 月,Linux 将迎来它的 30 周年纪念日!在这个过程中,你是在什么时候开始意识到 Linux 并不仅仅是一个“爱好”的?</strong></p>
<p><strong>Linus Torvalds:</strong> 这听起来可能有点荒谬,实际上我很早就开始意识到了。在 1991 年末(以及 1992 年初),Linux 已经比我预想的要大得多。</p>
<p>那时候可能只有几百个用户(确切地说不是“用户”,因为人们还要不断地对它进行修修补补),从没想过 Linux 后来能够发展壮大。在我看来,最大的转折点是当我意识到其他人正在使用它,并对它感兴趣,它开始有了自己的生命。人们开始发送补丁,这个系统能做的事情比我最初预想的要多得多。</p>
<p>1992 年 4 月的某个时候,X11 被移植到 Linux 上(其实我也记不太清具体时间了,毕竟那是很久以前的事了),这是一个重大进步,Linux 系统突然间有了 GUI 和一系列全新的功能。</p>
<p>我一开始并没有什么大计划。这只是一个个人项目,并不是出于要开发一个新操作系统的伟大梦想。我当时只是想了解我的新 PC 硬件的来龙去脉。</p>
<p>所以,在发布第一个版本时,实际上更多的是想“看看自己都做了些什么”。当然,我希望其他人会觉得它有趣,但它并不是一个真正可用的操作系统。它更多的是一种概念验证,而且只是一个我在当时做了几个月的个人项目。</p>
<p>从“个人项目”到其他人开始使用它、给我反馈(和 bug 报告)和发送补丁,对我来说是一个巨大的转变。</p>
<p>举个最基本的例子:最初的版权许可是“你可以以源代码的形式发布它,但不能用它赚钱”。</p>
<p>对于当时的我来说,商业版 Unix 太贵了(作为穷学生,我已经为了买新 PC 花光了所有钱),所以我希望这个操作系统的源代码是公开可用的(这样人们就可以提供补丁),我希望将它开放给像我这样负担不起昂贵电脑和操作系统的人。</p>
<p>1991 年末(或是 1992 年初),我把许可改为 GPLv2,因为有人想把它以软盘的形式分发给本地 Unix 用户组,但又想收回软盘的成本,并补偿他们拷贝软盘所花费的时间。我觉得这很合理,因为“免费”与否并不是最重要的,最重要的是要“公开源码”。</p>
<p>最终的结果是:人们不仅在 Unix 用户组中发布它,在几个月之内还出现了 SLS 和 Slackware 的软盘发行版。</p>
<p>与最初的那些根本性的变化相比,后来的一切都是“增量式”的。当然,有些增量式的变化也是大跨步(IBM 的加入、Oracle 数据库的移植、Red Hat 的首次公开募股,Android 在手机上的应用,等等),但在我看来,它们仍然不如最初的“我不认识的人都在使用 Linux”那样具有革命性。</p>
<p><strong>Jeremy Andrews:你是否曾经后悔修改了许可协议?或者说,其他人或公司用你开发的系统赚了很多钱,你因此感到后悔吗?</strong></p>
<p><strong>Linus Torvalds:</strong> 我从来没有后悔过。</p>
<p>首先,我过得还不错。我不是特别富有,但我是一个薪水很高的软件工程师,可以按照自己的节奏做我喜欢做的事情。</p>
<p>关键是我百分之百认为这个许可是 Linux(以及 Git)取得成功的重要原因。我认为,当所有人都认为他们有平等的权利,没有人在这方面有特权的时候,他们才会变得更快乐。</p>