-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
5162 lines (4966 loc) · 713 KB
/
search.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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Tacotron2声谱预测网络</title>
<url>/2022/03/15/tacotron2%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/</url>
<content><![CDATA[<h1 id="Tacotron2-声谱预测网络"><a href="#Tacotron2-声谱预测网络" class="headerlink" title="Tacotron2 声谱预测网络"></a><a href="https://arxiv.org/abs/1712.05884">Tacotron2 声谱预测网络</a></h1><h2 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h2><p>1.<a href="https://cloud.tencent.com/developer/article/1143127">注意力机制</a></p>
<p>2.<a href="https://blog.csdn.net/v_july_v/category_9261611.html">RNN</a></p>
<p>3.CNN</p>
<ol start="4">
<li>LSTM</li>
</ol>
<p>5.<a href="https://www.pianshen.com/article/93901929427/">梅尔波形(mel spectrogram)——一种中间声学特征表示</a></p>
<h2 id="模型概述"><a href="#模型概述" class="headerlink" title="模型概述"></a>模型概述</h2><p>该语音合成系统由两个组件组成,如下图所示</p>
<ol>
<li>一个循环 seq2seq 声谱特征预测网络,它从输入字符序列预测 mel 谱图帧的序列;</li>
<li>一个改进的 WaveNet,它根据预测的 mel 谱图帧生成时域波形样本。</li>
</ol>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202203031055188.png" alt="image-20220303105518888"></p>
<h2 id="梅尔谱图"><a href="#梅尔谱图" class="headerlink" title="梅尔谱图"></a>梅尔谱图</h2><p> 我们选择了一个低电平的声学表示:梅尔-频谱图,以连接这两个成分。使用一种很容易从时域波形计算出来的表示形式,可以让我们分别训练这两个分量。这种表示也比波形样本更平滑,更容易训练使用平方误差损失,因为它是不变的相位在每一帧。mel 谱图与线性谱图有关,即短时傅里叶变换(STFT)幅度。</p>
<p> 该方法是根据人类听觉系统的实测响应,对短时傅立叶变换的频率轴进行非线性变换,以较少的维数总结频率内容。</p>
<p>使用这种听觉频率标度可以强调对语音清晰度至关重要的低频细节,同时减少了对高频细节的强调,高频细节主要由摩擦和其他噪声爆发控制,通常不需要以高保真度建模。由于这些特性,从 mel 尺度衍生出来的特征已经被用作语音识别的基础表示几十年了</p>
<p>我们使用跨越 125 Hz 至 7.6 kHz 的 80 通道 mel 滤波器组将 STFT 幅度转换为梅尔尺度,然后进行对数动态范围压缩。在日志压缩之前,为了限制对数域的动态范围,滤波器组的输出幅度被裁剪到最小值 0.01。</p>
<h2 id="声谱预测网络"><a href="#声谱预测网络" class="headerlink" title="声谱预测网络"></a>声谱预测网络</h2><h3 id="结构概述"><a href="#结构概述" class="headerlink" title="结构概述"></a>结构概述</h3><p>该网络由编码器(Encoder)和解码器(Decoder)组成。编码器将字符序列转换为隐藏的特征表示,解码器使用该特征表示来预测谱图。输入字符使用学习过的 512 维字符嵌入(a learned 512-dimensional character embedding)表示,该字符嵌入经过 3 个卷积层的堆栈,每个卷积层包含 512 个形状为 5 x 1 的滤波器(filter),即每个滤波器跨越 5 个字符,然后是批量归一化和 ReLU 激活。</p>
<h3 id="编码器(Encoder)"><a href="#编码器(Encoder)" class="headerlink" title="编码器(Encoder)"></a>编码器(Encoder)</h3><p>编码器输出由注意力网络消耗,该网络将完整编码序列总结为每个解码器输出步骤的固定长度上下文向量。我们使用位置敏感注意(location-sensitive attention),它扩展了可加性注意机制将先前解码器时间步骤中的累积注意权值作为额外的特征。这鼓励模型在输入过程中始终向前移动,减少解码程序重复或忽略某些子序列的潜在失败模式。在投射输入后计算注意力概率</p>
<blockquote>
<p>在 Tacotron2 中,编码器将输入序列 X=[x<del>1</del>,x<del>2</del>,…,x<del>Tx</del>] 映射成序列 H=[h<del>1</del>,h<del>2</del>,…,h<del>Tx</del>] ,其中序列 H 被称作“编码器隐状态”(encoder hidden states)。注意:编码器的输入输出序列都拥有相同的长度,h<del>i</del>之于相邻分量 h<del>j</del>拥有的信息等价于 x<del>i</del>之于 x<del>j</del>所拥有的信息。</p>
<p>在 Tacotron2 中,每一个输入分量 xi 就是一个字符。Tacotron2 的编码器是一个 3 层卷积层后跟一个双向 LSTM 层形成的模块,在 Tacotron2 中卷积层给予了神经网络类似于 N−gramN−gram 感知上下文的能力。这里使用卷积层获取上下文主要是由于实践中 RNN 很难捕获长时依赖,并且卷积层的使用使得模型对不发音字符更为鲁棒(如’know’中的’k’)。</p>
<p>经词嵌入(word embedding)的字符序列先送入三层卷积层以提取上下文信息,然后送入一个双向的 LSTM 中生成编码器隐状态,即:</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202203031438629.png" alt="image-20220225155829121"></p>
<p>其中,F1、F2、F3为 3 个卷积核,ReLUReLU 为每一个卷积层上的非线性激活 E¯ 表示对字符序列 X 做 embedding,EncoderRecurrency 表示双向 LSTM。</p>
</blockquote>
<h3 id="Tacotron2-注意力机制-Location-Sensitive-Attention"><a href="#Tacotron2-注意力机制-Location-Sensitive-Attention" class="headerlink" title="Tacotron2 注意力机制-Location Sensitive Attention"></a>Tacotron2 注意力机制-Location Sensitive Attention</h3><blockquote>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202203031438800.png" alt="image-20220225160230769"></p>
<p>其中,s<del>i</del>为<em>当前</em>解码器隐状态而非上一步解码器隐状态,偏置值 b 被初始化为 0。位置特征 fi 使用累加注意力权重 cα<del>i</del>卷积而来</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202203031438516.png" alt="image-20220225160333596"></p>
</blockquote>
<h3 id="解码器(Decoder)"><a href="#解码器(Decoder)" class="headerlink" title="解码器(Decoder)"></a>解码器(Decoder)</h3><p>该解码器是一种自回归递归神经网络,它从编码后的输入序列一帧一帧地预测 mel 谱图。</p>
<p>其处理步骤如下:</p>
<ol>
<li>前一个时间步的预测首先通过一个包含 2 个完全连接的 256 个隐藏 ReLU 单元的 pre-net。</li>
<li>将 pre-net 输出和注意上下文向量串联起来,并通过一个包含 1024 个单元的 2 个单向 LSTM 层的堆栈。</li>
<li>将 LSTM 输出与注意上下文向量串联起来,通过线性变换进行投影,对目标谱图帧进行预测。</li>
<li>最后,将预测的 mel 谱图通过 5 层卷积 post-net(5-layer convolutional post-net)进行预测,并将残差添加到预测中以改善预测的梅尔谱图质量。post-net 由 512 个滤波器组成,形状为 5 x 1,批量归一化,然后在最后一层上进行 tanh 激活。</li>
</ol>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202203031055369.png" alt="image-20220303105542062"></p>
<h2 id="声码器(Vocoder)"><a href="#声码器(Vocoder)" class="headerlink" title="声码器(Vocoder)"></a>声码器(Vocoder)</h2><p>该论文使用的声码器是一个改进的 WaveNet,不是本次论文重点提出的模型,也不是我们本次项目使用到的声码器模型。故略。之后再对 melgan 等声码器进行分析。</p>
]]></content>
<categories>
<category>TTS(text to speech)</category>
</categories>
<tags>
<tag>TTS</tag>
<tag>AI</tag>
<tag>MachineLearning</tag>
<tag>DeepLearning</tag>
</tags>
</entry>
<entry>
<title>CPU虚拟化</title>
<url>/2021/03/11/CPU%E8%99%9A%E6%8B%9F%E5%8C%96/</url>
<content><![CDATA[<h1 id="CPU-虚拟化"><a href="#CPU-虚拟化" class="headerlink" title="CPU 虚拟化"></a>CPU 虚拟化</h1><h2 id="第二章—操作系统介绍"><a href="#第二章—操作系统介绍" class="headerlink" title="第二章—操作系统介绍"></a>第二章—操作系统介绍</h2><h3 id="操作系统的任务"><a href="#操作系统的任务" class="headerlink" title="操作系统的任务"></a><strong>操作系统的任务</strong></h3><p>操作系统:负责确保系统既易于使用又正确高效地运行。</p>
<p>它取得 CPU、内存或磁盘等物理资源,甚对它们进行虚拟化。</p>
<p>它处理与并发有关的麻烦且棘手的问题。</p>
<p>它持久地(persistently)存储文件,并保证其安全性。</p>
<h3 id="虚拟化"><a href="#虚拟化" class="headerlink" title="虚拟化"></a>虚拟化</h3><p>将物理资源转化为更通用、更强大且更易于使用的虚拟形式。我们有时候将操作系统称为<strong>虚拟机</strong>。</p>
<p>为了让应用程序告诉操作系统要做什么,操作系统<strong>提供了许多 API</strong>(接口),有时候也说是操作系统为应用程序提供了一个标准库</p>
<p>操作系统也被称为<strong>资源管理器</strong>。他让多个程序运行,共享 cpu,让许多程序访问设备,同时访问自己的指令和数据</p>
<p><strong>虚拟化 CPU</strong></p>
<p>将单个 CPU(或其中一小部分)转换为看似无限数量的 CPU,从而让许多程序看似同时运行,这就是所谓的虚拟化 CPU</p>
<p><strong>虚拟化内存</strong></p>
<p>每个进程都有自己的私有虚拟地址空间,操作系统以某种方式映射到机器的物理内存上,一个正在运行的程序的内存引用不会影响其他进程。</p>
<blockquote>
<p><strong>CPU</strong>:通常使用时间片、多核的方法达到对 CPU 的分割;<br><strong>内存</strong>:内存是 CPU 可以进行直接寻址的存储空间,通常使用分段、分页的手段达到逻辑分割;<br>IO:即输入\输出,以网卡、磁盘为例:<br><strong>磁盘</strong>:采用磁盘映像文件的方式实现分割,通常采用 Spare 格式(稀疏格式:牺牲性能,虚拟化超出本身的内存空间)<br><strong>网卡</strong>:通过软件的方式,获得虚拟化网卡。</p>
</blockquote>
<h3 id="设计目标"><a href="#设计目标" class="headerlink" title="设计目标"></a>设计目标</h3><p>1.建立一些抽象,让系统方便使用</p>
<p>2.提供高性能</p>
<p>3.在应用程序和 OS 之间,以及应用程序之间提供保护</p>
<p>4.高度的可靠性和安全性</p>
<h2 id="第四章—抽象:进程"><a href="#第四章—抽象:进程" class="headerlink" title="第四章—抽象:进程"></a>第四章—抽象:进程</h2><h3 id="知识点"><a href="#知识点" class="headerlink" title="知识点"></a>知识点</h3><p><strong>进程非正式定义</strong></p>
<p>进程就是运行中的程序</p>
<p><strong>时分共享 cpu 技术</strong></p>
<p>通过让一个进程只运行一个时间片,然后切换到其他进程,操作系统提供了存在多个虚拟 CPU 的假象</p>
<p><strong>上下文切换</strong></p>
<p>它让操作系统能够停止运行一个程序,并开始在给定的 CPU 上运行另一个程序。</p>
<p><strong>抽象-进程</strong></p>
<p>操作系统为正在运行的程序提供的抽象,就是所谓的进程。进程是操作系统进行资源分配和调度的一个独立单位</p>
<p>进程的机器状态:</p>
<ol>
<li>内存,进程可以访问的内存(称为地址空间,address space)是该进程的一部分。</li>
<li>寄存器:许多指令明确地读取或更新寄存器。</li>
<li>还有一些特殊的寄存器,如 PC,栈指针,帧指针。</li>
</ol>
<p><strong>一个进程包括五个部分</strong></p>
<ol>
<li>(OS 管理运行程序的)数据结构 P</li>
<li>(运行程序的)内存代码 C</li>
<li>(运行程序的)内存数据 D</li>
<li>(运行程序的)通用寄存器信息 R</li>
<li>(OS 控制程序执行的)程序状态字信息 PSW</li>
</ol>
<p><strong>进程 API</strong></p>
<p>创建(create)<br>销毁(destroy)<br>等待(wait)<br>其他控制(miscellaneous control)<br>状态(status)</p>
<p><strong>进程创建</strong></p>
<p>1、将代码和所有静态数据(例如初始化变量)加载(load)到内存中,加载到进程的地址空间中。</p>
<p>2、为程序的运行时栈(run-time stack 或 stack)分配一些内存</p>
<p>3、也可能为程序的堆(heap)分配一些内存</p>
<p>4、一些其他初始化任务,特别是与输入/输出(I/O)相关的任务</p>
<p>5、启动程序,在入口处运行,即 main()。通过跳转到 main()例程(第 5 章讨论的专门机制),OS 将 CPU 的控制权转移到新创建的进程中,从而程序开始执行。</p>
<p><strong>进程的状态</strong></p>
<p>三种基本状态:</p>
<p>1、运行(running):在运行状态下,进程正在处理器上运行。这意味着它正在执行指令。<br>2、就绪(ready):在就绪状态下,进程已准备好运行,但由于某种原因,操作系统选择不在此时运行。<br>3、阻塞(blocked):在阻塞状态下,一个进程执行了某种操作,直到发生其他事件时才会准备运行。一个常见的例子是,当进程向磁盘发起 I/O 请求时,它会被阻塞,因此其他进程可以使用处理器。</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111111301668.png" alt="image-20211111130118589"></p>
<p>其他可能存在的状态:</p>
<p><strong>挂起状态</strong>(是该进程暂时不接受调度)。</p>
<p><strong>创建状态</strong>:此时,进程已经拥有了字节的 PCB,但该进程所必需的资源或其它信息(如主存资源)尚未分配,进程自身还未进入主存,即创建工作尚未完成,进程还不能够被调度运行。</p>
<p>(创建进程的两个步骤: 为一个新进程创建 PCB,并填写必要管理信息;把该进程转入就绪状态并插入就绪队列。)</p>
<p><strong>终止状态</strong>:进程的终止首先要等待操作系统进行善后处理,然后将其 PCB 清零,并将 PCB 空间返还系统。</p>
<p><strong>进程控制块(Process Control Block)</strong></p>
<p>PCB 是 OS 用于记录和刻画进程状态及环境信息的数据结构</p>
<p>借助 PCB,OS 可以全面管理进程物理实体,刻画进程的执行现状,控制进程的执行</p>
<h3 id="课后习题"><a href="#课后习题" class="headerlink" title="课后习题"></a>课后习题</h3><p><strong>4.1</strong></p>
<p><strong>用以下标志运行程序:./process-run.py -l 5:100,5:100。CPU 利用率(CPU 使用时间的百分比)应该是多少?为什么你知道这一点?利用 -c 标记查看你的答案是否正确。</strong></p>
<p>运行结果如下</p>
<p>**cpu 利用率为 100%**,可以看到进程 0 和进程 1 交替执行,且没有调用 IO 命令。</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943336.png" alt="image-20211107224505762"></p>
<p>加上-c 标记后运行结果。CPU 一直处于占用状态,验证正确</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943337.png" alt="image-20211107224328317"></p>
<p><strong>4.2</strong></p>
<p><strong>现在用这些标志运行:./process-run.py -l 4:100,1:0。这些标志指定了一个包含 4 条指令的进程(都要使用 CPU),并且只是简单地发出 I/O 并等待它完成。完成这两个进程需要多长时间?利用-c 检查你的答案是否正确。</strong></p>
<p>从运行结果来看,进程 0 的 4 条指令需要占用 4 个 cpu 时间,发起 io 和 io 结束需要 2 个时装周期 ,io 占用时间还无法得知。total=4+2+n</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943338.png"></p>
<p>加上-c 标记后运行结果,可以看到程序一共占用了 11 个时钟周期,io 占用了 5 时钟周期</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111120937254.png"></p>
<p><strong>4.3</strong></p>
<p><strong>现在交换进程的顺序:./process-run.py -l 1:0,4:100。现在发生了什么?交换顺序是否重要?为什么?同样,用-c 看看你的答案是否正确。</strong></p>
<p>交换运行顺序后,首先运行进程 0,进程 0 发起 IO,系统进行上下文切换,进程 1 占用 cpu 执行程序。</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943340.png" alt="image-20211109170627689"></p>
<p>加上-c,可以看到,让进程 0 先执行 IO 的话,进程 1 在进程 0 调用 IO 的同时可占用 CPU。只需 6 个时钟周期即可执行完程序</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943341.png" alt="image-20211109171420829"></p>
<p><strong>4.4</strong></p>
<p><strong>现在交换进程的顺序:./process-run.py -l 1:0,4:100。现在发生了什么?交换顺序是否重要?为什么?同样,用-c 看看你的答案是否正确。</strong></p>
<p>可以看到,因为进程在进行 I/O 操作时,系统不会进行上下文切换,所以进程 1 只能等待进程 0 调用 IO 结束再占用 CPU,最终要消耗 9 个时钟周期</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943342.png" alt="image-20211109172648650"></p>
<p><strong>4.5</strong></p>
<p><strong>现在,运行相同的进程,但切换行为设置,在等待 I/O 时切换到另一个进程(-l 1:0,4:100-c -S SWITCH_ON_IO)。现在会发生什么?利用-c 来确认你的答案是否正确。</strong></p>
<p>可以看到,让进程 0 先执行 IO,然后系统进行上下文切换,进程 1 可占用 CPU。只需 6 个时钟周期即可执行完程序</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943343.png" alt="image-20211109172752093"></p>
<h2 id="第五章-进程-API"><a href="#第五章-进程-API" class="headerlink" title="第五章-进程 API"></a>第五章-进程 API</h2><h3 id="知识点-1"><a href="#知识点-1" class="headerlink" title="知识点"></a>知识点</h3><h4 id="fork-系统调用"><a href="#fork-系统调用" class="headerlink" title="fork 系统调用"></a><strong>fork 系统调用</strong></h4><p>1、子进程不会从 main()函数开始执行,而是直接从 fork()系统调用返回。</p>
<p>2、子进程拥有自己的地址空间(即拥有自己的私有内存)、寄存器、程序计数器等</p>
<p>3、父进程获得的返回值是新创建子进程的 PID,而子进程获得的返回值是 0</p>
<p>4、子进程和父进程的运行顺序取决于 CPU 调度顺序</p>
<p><strong>代码实现</strong></p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><unistd.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"hello world (pid:%d)\n"</span>, (<span class="type">int</span>) getpid());</span><br><span class="line"><span class="type">int</span> rc = fork();</span><br><span class="line"><span class="keyword">if</span> (rc < <span class="number">0</span>) { <span class="comment">// fork failed; exit</span></span><br><span class="line"><span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"fork failed\n"</span>);</span><br><span class="line"><span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (rc == <span class="number">0</span>) { <span class="comment">// child (new process)</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"hello, I am child (pid:%d)\n"</span>, (<span class="type">int</span>) getpid());</span><br><span class="line">} <span class="keyword">else</span> { <span class="comment">// parent goes down this path (main)</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"hello, I am parent of %d (pid:%d)\n"</span>,</span><br><span class="line">rc, (<span class="type">int</span>) getpid());</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="wait-系统调用"><a href="#wait-系统调用" class="headerlink" title="wait 系统调用"></a>wait 系统调用</h4><p>父进程调用 wait(),延迟自己的执行,直到子进程执行完毕。当子进程结束,wait()才返回父进程。</p>
<p>代码实现</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><unistd.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sys/wait.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span><br><span class="line"> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"hello world (pid:%d)\n"</span>, (<span class="type">int</span>) getpid());</span><br><span class="line"> <span class="type">int</span> rc = fork();</span><br><span class="line"> <span class="keyword">if</span> (rc < <span class="number">0</span>) { <span class="comment">// fork failed; exit</span></span><br><span class="line"> <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"fork failed\n"</span>);</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (rc == <span class="number">0</span>) { <span class="comment">// child (new process)</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"hello, I am child (pid:%d)\n"</span>, (<span class="type">int</span>) getpid());</span><br><span class="line"> } <span class="keyword">else</span> { <span class="comment">// parent goes down this path (main)</span></span><br><span class="line"> <span class="type">int</span> wc = wait(<span class="literal">NULL</span>);</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"hello, I am parent of %d (wc:%d) (pid:%d)\n"</span>,</span><br><span class="line"> rc, wc, (<span class="type">int</span>) getpid());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>运行结果</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">prompt> ./p2</span><br><span class="line">hello world (pid:29266)</span><br><span class="line">hello, I am child (pid:29267)</span><br><span class="line">hello, I am parent of 29267 (wc:29267) (pid:29266)</span><br><span class="line">prompt></span><br></pre></td></tr></table></figure>
<h4 id="exec-系统调用"><a href="#exec-系统调用" class="headerlink" title="exec 系统调用"></a>exec 系统调用</h4><p>这个系统调用可以让子进程执行与父进程不同的程序。例如,在 p2.c 中调用 fork(),这只是在你想运行相同程序的拷贝谁有用。但是,我们常常想运行不同的程序,exec()正好做这样的事</p>
<p>这个例子中,子进程调用 execvp()来运行字符计数程序 wc。</p>
<p>exec()会从可执行程序中加载代码和静态数据,并用它覆写自己代码段(以及静态数据),堆、栈及其他内存空间也会被重新初始化。然后操作系统就执行该程序,将参数通过 argv 传递给该进程。因此,它并没有创建新进程,而是直接将当前运行的程序(以前的 p3)替换为不同的运行程序(wc)。子进程执行 exec()之后,几乎就像 p3.c 从未运行过一样。对 exec()的成功调用永远不会返回</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">prompt> ./p3</span><br><span class="line">hello world (pid:29383)</span><br><span class="line">hello, I am child (pid:29384)</span><br><span class="line">29 107 1030 p3.c</span><br><span class="line">hello, I am parent of 29384 (wc:29384) (pid:29383)</span><br><span class="line">prompt></span><br></pre></td></tr></table></figure>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><unistd.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sys/wait.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span></span><br><span class="line"><span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span></span><br><span class="line">{</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"hello world (pid:%d)\n"</span>, (<span class="type">int</span>) getpid());</span><br><span class="line"><span class="type">int</span> rc = fork();</span><br><span class="line"><span class="keyword">if</span> (rc < <span class="number">0</span>) { <span class="comment">// fork failed; exit</span></span><br><span class="line"><span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">"fork failed\n"</span>);</span><br><span class="line"><span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (rc == <span class="number">0</span>) { <span class="comment">// child (new process)</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"hello, I am child (pid:%d)\n"</span>, (<span class="type">int</span>) getpid());</span><br><span class="line"><span class="type">char</span> *myargs[<span class="number">3</span>];</span><br><span class="line">myargs[<span class="number">0</span>] = strdup(<span class="string">"wc"</span>); <span class="comment">// program: "wc" (word count)</span></span><br><span class="line">myargs[<span class="number">1</span>] = strdup(<span class="string">"p3.c"</span>); <span class="comment">// argument: file to count</span></span><br><span class="line">myargs[<span class="number">2</span>] = <span class="literal">NULL</span>; <span class="comment">// marks end of array</span></span><br><span class="line">execvp(myargs[<span class="number">0</span>], myargs); <span class="comment">// runs word count</span></span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"this shouldn't print out"</span>);</span><br><span class="line">} <span class="keyword">else</span> { <span class="comment">// parent goes down this path (main)</span></span><br><span class="line"><span class="type">int</span> wc = wait(<span class="literal">NULL</span>);</span><br><span class="line"><span class="built_in">printf</span>(<span class="string">"hello, I am parent of %d (wc:%d) (pid:%d)\n"</span>,</span><br><span class="line">rc, wc, (<span class="type">int</span>) getpid());</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="why-splits-fork-and-exec"><a href="#why-splits-fork-and-exec" class="headerlink" title="why splits fork() and exec()"></a>why splits fork() and exec()</h4><p>在构建 UNIX shell 的时候非常有用,因为这给了 shell 在 fork 之后 exec 之前运行代码的机会,这些代码可以在运行新程序前改变环境,实现一些有趣的功能。</p>
<p>例<code>prompt> wc p3.c > newfile.txt</code><br>在上面的例子中,wc 的输出结果被重定向(redirect)到文件 newfile.txt 中(通过 newfile.txt 之前的大于号来指明重我向)。shell 实现结果重定向的方式也很简单,当完成子进程的创建<br>后,shell 在调用 exec()之前先关闭了标准输出(standardoutput),打开了文件 newfile.txt。这样,即将运行的程序 wc 的输出结果就被发送到该文件,而不是打印在屏幕上。</p>
<h3 id="课后习题-1"><a href="#课后习题-1" class="headerlink" title="课后习题"></a>课后习题</h3><p><strong>5.1</strong></p>
<p><strong>编写一个调用 fork()的程序。谁调用 fork()之前,让主进程访问一个变量(例如 x)并将其值设置为某个值(例如 100)。子进程中的变量有什么值?当子进程和父进程都改变 x 的值,变量会发生什么?</strong></p>
<p>测试代码:</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><unistd.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">test1</span><span class="params">()</span> {<span class="comment">//在调用之前,让主进程访问一个变量(例如 x)并将其值设 //置为100</span></span><br><span class="line"> <span class="type">int</span> pid = fork();</span><br><span class="line"> <span class="type">int</span> x = <span class="number">100</span>;</span><br><span class="line"> <span class="keyword">if</span> (pid < <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"fork error\n"</span>);</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"child, x %d\n"</span>, x);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"parent, x %d\n"</span>, x);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">test2</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">int</span> pid = fork();</span><br><span class="line"> <span class="type">int</span> x = <span class="number">100</span>;</span><br><span class="line"> <span class="keyword">if</span> (pid < <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"fork error\n"</span>);</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) { <span class="comment">//当子进程和父进程都改变 x 的值时</span></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"child,x %d\n"</span>, x);</span><br><span class="line"> x = <span class="number">0</span>;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"child,x %d\n"</span>, x);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"parent,x %d\n"</span>, x);</span><br><span class="line"> x = <span class="number">1</span>;</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"parent,x %d\n"</span>, x);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> {</span><br><span class="line"> <span class="keyword">if</span> (argc == <span class="number">1</span>) {</span><br><span class="line"> test1();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> test2();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>可以看到,在调用之前,让主进程访问一个变量(例如 x)并将其值设为 100,父进程和子进程的 x 值都为 100</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943344.png" alt="image-20211109174929408"></p>
<p>当子进程和父进程在各自进程修改 x 值时,父子进程的值各自不受影响</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943345.png" alt="image-20211109173934825"></p>
<p><strong>5.2</strong></p>
<p><strong>编写一个打开文件的程序(使用 open()系统调用),然后调用 fork()创建一个新进程。子进程和父进程都可以访问 open()返回的文件描述符吗?当它在并发(即同时)写入文件时,会发生什么?</strong></p>
<p>测试代码:</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><fcntl.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdio.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><stdlib.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><string.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sys/wait.h></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><unistd.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">int</span> fd = open(<span class="string">"./check.txt"</span>, O_CREAT | O_RDWR | O_TRUNC, S_IRWXU);</span><br><span class="line"> <span class="type">int</span> pid = fork();</span><br><span class="line"> <span class="keyword">if</span> (pid < <span class="number">0</span>) {</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"fork error\n"</span>);</span><br><span class="line"> <span class="built_in">exit</span>(<span class="number">1</span>);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (pid == <span class="number">0</span>) {</span><br><span class="line"> <span class="type">char</span> *buf = <span class="string">"child\n"</span>;</span><br><span class="line"> <span class="type">int</span> error = write(fd, buf, <span class="keyword">sizeof</span>(<span class="type">char</span>) * <span class="built_in">strlen</span>(buf));</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"child error: %d\n"</span>, error == <span class="number">-1</span> ? <span class="number">1</span> : <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="type">char</span> *buf = <span class="string">"parent\n"</span>;</span><br><span class="line"> <span class="type">int</span> error = write(fd, buf, <span class="keyword">sizeof</span>(<span class="type">char</span>) * <span class="built_in">strlen</span>(buf));</span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"child error: %d\n"</span>, error == <span class="number">-1</span> ? <span class="number">1</span> : <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> wc = wait(<span class="literal">NULL</span>);</span><br><span class="line"> close(fd);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>运行结果如下,可以看到</p>
<p>父进程,子进程都能访问 open 返回的 fd 文件符。当他们同时写入文件时,存在竞争条件,但因为操作系统会进行调度,所以最终两个进程都能写入成功</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943346.png" alt="image-20211109180303635"></p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943347.png" alt="image-20211109180212533"></p>
<p><strong>5.4</strong></p>
<p><strong>编写一个调用 fork()的程序,然后调用某种形式的 exec()来运行序”/bin/ls”看看是否可以尝试 exec 的所有变体,包括 execl()、 execle()、 execlp()、 execv()、 execvp()和 execve(),为什么同样的基本调用会有这么多变种?</strong></p>
<p>测试代码</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">#include <stdio.h></span><br><span class="line">#include <stdlib.h></span><br><span class="line">#include <unistd.h></span><br><span class="line"></span><br><span class="line">int main(int argc, char *argv[], char *envp[]) {</span><br><span class="line"> int pid = fork();</span><br><span class="line"> char *cmd = "/bin/ls";</span><br><span class="line"> char *arg[] = {"ls", "-a", NULL};</span><br><span class="line"></span><br><span class="line"> if (pid < 0) {</span><br><span class="line"> printf("fork error\n");</span><br><span class="line"> exit(1);</span><br><span class="line"> } else if (pid == 0) {</span><br><span class="line"> // exec不会返回,所以第一条execl语句后的语句不会被执行</span><br><span class="line"> execl(cmd, "ls", NULL);</span><br><span class="line"> execlp(cmd, "ls", NULL);</span><br><span class="line"> execve(cmd, arg, envp);</span><br><span class="line"> execv(cmd, arg);</span><br><span class="line"> execvp(cmd, arg);</span><br><span class="line"> execle(cmd, "ls", NULL, envp);</span><br><span class="line"> execvP(cmd,arg)</span><br><span class="line"> } else {</span><br><span class="line"> }</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>以下是调用各个形式的 exec 函数后程序的运行结果</p>
<p>exec 多个变体能提供不同的功能,不同后缀指示着不同的操作功能:</p>
<ol>
<li>l: 希望接收以逗号分隔的参数列表,列表以 NULL 指针作为结束标志</li>
<li>v: 希望接收一个以 NULL 结尾字符串数组的指针</li>
<li>p: 是一个以 NULL 结尾的字符串数组指针,函数可以利用 DOS 的 PATH 变量查找自程序文件</li>
<li>e: 函数传递指定采纳数 envp(环境变量),允许改变子进程环境,无后缀 e 是,子进程使用当前程序环境</li>
<li>c 语言没有默认参数语法,只能实现多个变体</li>
</ol>
<p>execl:</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943348.png" alt="image-20211109195822756"></p>
<p>execlp:</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943349.png" alt="image-20211109195938552"></p>
<p>execve:</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943350.png" alt="image-20211109200052709"></p>
<p>execv:</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943351.png" alt="image-20211109200128674"></p>
<p>execvp:</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943352.png" alt="image-20211109200209564"></p>
<p>execle:</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943353.png" alt="image-20211109200430882"></p>
<p>execlP:</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943354.png" alt="image-20211109200543452"></p>
<h2 id="第六章——受限直接执行"><a href="#第六章——受限直接执行" class="headerlink" title="第六章——受限直接执行"></a>第六章——受限直接执行</h2><h3 id="受限运行协议"><a href="#受限运行协议" class="headerlink" title="受限运行协议"></a>受限运行协议</h3><p><strong>用户模式</strong> user mode:在用户模式下运行的代码会受到限制,如用户模式下,进程不能发出 I/O,这样会引发异常,可能导致操作系统终止该进程</p>
<p><strong>内核模式</strong> kernel mode:操作系统就在内核模式下运行,在此模式下,运行的代码可以做它想做的所有事,包括特权操作,发出 I/O 和执行所有类型的受限制操作</p>
<p>受限直接运行<strong>有两个阶段</strong>:<br>1,系统引导时:内核初始化陷阱表,CPU 记住陷阱表的位置以供使用<br>2,运行进程时:在执行进程前,操作系统为进程初始化了一些内容,接着转入用户模式运行程序。当进程发起系统调用时,会重新陷入操作系统,然后通过陷阱返回,并将控制权重新交给进程</p>
<p>通过让进程在用户模式和内核模式间切换,就完成了保护操作系统的控制权,且限制进程的运行,让它不能做不应该做的事</p>
<p><img src="https://s2.loli.net/2022/01/09/k2O1dHJv7jxi5Is.png" alt="image-20220109081403999"></p>
<p><img src="https://s2.loli.net/2022/01/09/IHiywGmulDKWvEU.png" alt="image-20220109081547409"></p>
<p>操作系统通过时钟中断重新获得了 CPU 的控制权,那么它需要决定:是继续运行当前的进程,还是切换其它进程,这个决定是由调度程序做出的,它是操作系统的一部分,这里先说明如何切换进程</p>
<p>如果决定切换进程,OS 就会执行一些底层代码,即上下文切换:操作系统为当前正执行的进程保存它寄存器的值,并为即将执行的进程恢复寄存器的值,这样操作系统可以确保从内核模式返回时,去执行另一个进程而不是之前运行的进程</p>
<p>下面是进程 A,B 间切换的一张表,操作系统决定从当前正在运行的进程 A 切换到进程 B,它调用 switch(),该系统调用会保存当前运行进程的寄存器的值(保存到 A 的进程结构),将 B 的进程结构恢复到寄存器,从内核模式退出到用户模式,进入 B 进程执行代码</p>
<h2 id="第七章——进程调度"><a href="#第七章——进程调度" class="headerlink" title="第七章——进程调度"></a>第七章——进程调度</h2><h3 id="知识点-2"><a href="#知识点-2" class="headerlink" title="知识点"></a>知识点</h3><h4 id="调度指标"><a href="#调度指标" class="headerlink" title="调度指标"></a><strong>调度指标</strong></h4><p>周转时间:T <del>周转时间</del>= T <del>完成时间</del>−T <del>到达时间</del></p>
<p>响应时间:T<del>响应时间</del>= T <del>首次运行</del>−T <del>到达时间</del></p>
<p>公平性:每个进程都有得到调度的机会</p>
<h4 id="FIFO(先来先服务)"><a href="#FIFO(先来先服务)" class="headerlink" title="FIFO(先来先服务)"></a>FIFO(先来先服务)</h4><p>优点:它很简单,而且易于实现</p>
<p>缺点:护航效应,一些耗时较少的潜在资源消费者被排在重量级的资源消费者之后。导致很差的平均周转时间</p>
<h4 id="SJF(最短任务优先)"><a href="#SJF(最短任务优先)" class="headerlink" title="SJF(最短任务优先)"></a>SJF(最短任务优先)</h4><p>考虑到所有工作同时到达的假设,先运行最短的任务,然后是次短的任务,如此下去。</p>
<p>我们可以证明 SJF 确实是一个最优的调度算法(假设所有任务同时到达)</p>
<h4 id="最短完成时间优先(STCF)"><a href="#最短完成时间优先(STCF)" class="headerlink" title="最短完成时间优先(STCF)"></a>最短完成时间优先(STCF)</h4><p>每当新工作进入系统时,它就会确定剩余工作和新工作中,谁的剩余时间最少,然后调度该工作。</p>
<p>允许抢占,放宽假设,所有任务不是同时到达的</p>
<p>优点:结果是平均周转时间大大提高,虑到我们的新假设,STCF 可证明是最优的。考虑到如果所有工作同时到达,SJF 是最优的</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111112050629.png" alt="image-20211111205039567"></p>
<h4 id="轮转(RR)"><a href="#轮转(RR)" class="headerlink" title="轮转(RR)"></a>轮转(RR)</h4><p>基本思想:RR 在一个时间片(time slice,有时称为调度 scheduling quantum)内运行一个工作,然后切换到运行队列中的下一个任务,而不是运行一个任务直到结束。它反复执行,直到所有任务完成。</p>
<p>【请注意,时间片长度必须是时钟中断周期的倍数。】</p>
<p>时间片长度对于 RR 是至关重要的。<br>时间片太短是有问题的:突然上下文切换的成本将影响整体性能。因此,系统设计者需要权衡时间片的长度,使其足够长,以便摊销(amortize)上下文切换成本,而又不会使系统不及时响应。</p>
<p>优势:有非常好的平均响应时间,通过设置合适的时间片,能获得不错的性能</p>
<p>缺点:较差的平均周转时间</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111230810271.png" alt="image-20211123081045212"></p>
<h4 id="结合-IO"><a href="#结合-IO" class="headerlink" title="结合 IO"></a><strong>结合 IO</strong></h4><p>当一个交互性进程发出 IO 让出 CPU 后,调度程序调度其他程序,从而更好地利用 CPU<img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111112104131.png" alt="image-20211111210429081"></p>
<h3 id="课后习题-2"><a href="#课后习题-2" class="headerlink" title="课后习题"></a>课后习题</h3><p><strong>7.1</strong></p>
<p><strong>使用 SJF 和 FIFO 调度程序运行长度为 200 的 3 个作业时,计算响应时间和周转时间。</strong></p>
<table>
<thead>
<tr>
<th>作业 ID</th>
<th>响应时间</th>
<th>周转时间</th>
</tr>
</thead>
<tbody><tr>
<td>SJF</td>
<td>平均响应时间:200</td>
<td>平均周转时间:400</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>200</td>
</tr>
<tr>
<td>2</td>
<td>200</td>
<td>400</td>
</tr>
<tr>
<td>3</td>
<td>400</td>
<td>600</td>
</tr>
<tr>
<td>FIFO</td>
<td>平均响应时间:200</td>
<td>平均周转时间:400</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>200</td>
</tr>
<tr>
<td>2</td>
<td>200</td>
<td>400</td>
</tr>
<tr>
<td>3</td>
<td>400</td>
<td>600</td>
</tr>
</tbody></table>
<p><strong>7.2</strong></p>
<p>现在做同样的事情,但有不同长度的作业,即 100、200 和 300</p>
<table>
<thead>
<tr>
<th>作业 ID</th>
<th>响应时间</th>
<th>周转时间</th>
</tr>
</thead>
<tbody><tr>
<td>SJF</td>
<td>平均响应时间:133.3</td>
<td>平均周转时间:333.3</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>100</td>
</tr>
<tr>
<td>2</td>
<td>100</td>
<td>300</td>
</tr>
<tr>
<td>3</td>
<td>300</td>
<td>600</td>
</tr>
<tr>
<td>FIFO</td>
<td>平均响应时间:133.3</td>
<td>平均周转时间:333.3</td>
</tr>
<tr>
<td>1</td>
<td>0</td>
<td>100</td>
</tr>
<tr>
<td>2</td>
<td>100</td>
<td>300</td>
</tr>
<tr>
<td>3</td>
<td>300</td>
<td>600</td>
</tr>
</tbody></table>
<p><strong>7.3</strong></p>
<p><strong>现在做同样的事情,但采用 RR 调度程序,时间片为 1</strong></p>
<table>
<thead>
<tr>
<th>作业 ID</th>
<th>响应时间</th>
<th>周转时间</th>
</tr>
</thead>
<tbody><tr>
<td>RR</td>
<td>平均响应时间:2</td>
<td>平均周转时间:599</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>598</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
<td>599</td>
</tr>
<tr>
<td>3</td>
<td>3</td>
<td>600</td>
</tr>
</tbody></table>
<p>当三件工作时间分别为 100,200,300 时</p>
<table>
<thead>
<tr>
<th>作业 ID</th>
<th>响应时间</th>
<th>周转时间</th>
</tr>
</thead>
<tbody><tr>
<td>RR</td>
<td>平均响应时间:2</td>
<td>平均周转时间:456.67</td>
</tr>
<tr>
<td>1</td>
<td>1</td>
<td>298</td>
</tr>
<tr>
<td>2</td>
<td>2</td>
<td>499</td>
</tr>
<tr>
<td>3</td>
<td>3</td>
<td>600</td>
</tr>
</tbody></table>
<p><strong>7.4</strong></p>
<p><strong>对于什么类型的工作负载,SJF 提供与 FIFO 相同的周转时间?</strong></p>
<p>以第一条:作业列表中的作业到达时间全部不一致。<br>第二,当作业到达时间一致时,在极细微可以忽略不计的时间上,作业列表中的作业排序必须按作业长度非严格递增。<br>第三,当有的作业到达时间一致,有的不一致时,到达时间一致的作业满足第二条。</p>
<p><strong>7.5</strong></p>
<p><strong>对于什么类型的工作负载和量子长度,SJF 与 RR 提供相同的响应时间?</strong></p>
<p>当运行时间小于等于时间片的时候,SJF 和 RR 提供相同的响应时间</p>
<p><strong>7.6</strong></p>
<p><strong>随着工作长度的增加,SJF 的响应时间会怎样?</strong></p>
<p>随着工作长度的增加,SJF 的响应时间越来越长</p>
<p><strong>7.7</strong></p>
<p><strong>随着量子长度的增加,RR 的响应时间会怎样?你能写出一个方程,计算给定 N 个工作时,最坏情况的响应时间吗?</strong></p>
<p>随着量子长度的增加,RR 的平均响应时间会增加</p>
<p>假设 k 个工作 n1,n2,n3..nk</p>
<p>工作长度为 t1,t2…tk 且 t1>t2>…tk</p>
<p>最坏情况的平均响应时间为</p>
<p>averT=(t1+t1+t2+t1+t2+t3+t1+t2+t3…)/k=((k-1)t1+(k-2)t2+…tk-1)/k</p>
<h2 id="第八章—调度:多级反馈队列(MLFQ)"><a href="#第八章—调度:多级反馈队列(MLFQ)" class="headerlink" title="第八章—调度:多级反馈队列(MLFQ)"></a>第八章—调度:多级反馈队列(MLFQ)</h2><h3 id="知识点-3"><a href="#知识点-3" class="headerlink" title="知识点"></a>知识点</h3><p><strong>MLFQ(多级反馈队列)</strong></p>
<p>有许多独立的队列,每个列有不同的优先级。并利用反馈信息决定某个工作的优先级。一个工作只能处于一个队列中。MLFQ 总是优先执行优先级高的工作。对于一个队列中的工作,我们采取轮转调度</p>
<p>规则 1:如果 A 的优先级大于 B 优先级,运行 A</p>
<p>规则 2:如果 A 的优先级=B,轮转运行 A 和 B</p>
<p><strong>尝:1:改变优先级</strong></p>
<p>规则 3:工作进入系统时,放在最高优先级</p>
<p>规则 4a:工作用完整个时间片后,降低其优先级(移入下一个队列)</p>
<p>规则 4b:如果工作在其时间片内主动释放 cpu,则优先级不变</p>
<p><strong>当前存在的问题</strong></p>
<p>饥饿问题,如果一个程序总是在时间片用完之前让出 cpu,那么它将永远占据高优先级,导致长工作饿死</p>
<p>存在愚弄程序,使一个程序总是在时间片用完之前让出 cpu,它将永远占据高优先级</p>
<p>一个程序不同时间表现不同,一个刚开始计算密集型程序可能在某段时间表现为交互性,那么他无法享受到交互性的待遇</p>
<p><strong>尝试 2:提升优先级</strong></p>
<p>周期性地提升(boost)所有工作的优先级</p>
<p>规则 5:经过一段时间 S,就将系统中所有工作重新加入最高优先级队列。</p>
<p><strong>尝试 3:更好的计时方式</strong></p>
<p>规则 4:一旦工作用完了其在某一层中的时间配额(无论中间主动放弃了多少次 CPU),就降低其优先级(移入低一级队列)</p>
<p><strong>其他问题</strong></p>
<p>1.如何配置一个调度程序,例如,配置多少队列?每一层队列的时间片配置多大?</p>
<p>大多数的 MLFQ 变体都支持不同队列可变的时间片长度。高优先级队列通常只有较短的时间片(比如 10ms 或者更少),因而这一层的交互工作可以更快地切换。相反,低优先级队列中更多的是 CPU 密集型工作,配置更长的时间片会取得更好的效果。</p>
<p><strong>MLFQ 优点</strong></p>
<p>1.它不需要对工作的运行方式有先验知识,而是通过观察工作的运行来给出对应的优先级</p>
<p>2.MLFQ 可以同时满足各种工作的需求:对于短时间运行的交互型工作,获得类似于 SJF/STCF 的很好的全局性能,同时对长时间运行的 CPU 密集型负载也可以公平地、不断地稳步向前。</p>
<p>因此,许多系统使用某种类型的 MLFQ 作为自己的基础调度程序,包括类 BSD UNIX 系统[LM+89,B86]、Solaris[M06]以及 WindowsNT 和其后的 Window 系列操作系统。</p>
<h3 id="课后习题-3"><a href="#课后习题-3" class="headerlink" title="课后习题"></a>课后习题</h3><p><strong>8.1</strong></p>
<p><strong>只用两个工作和两个队列运行几个随机生成的问题。针对每个工作计算 MLFQ 的执行记录。限制每项作业的长度并关闭 I/O,让你的生活更轻松。</strong></p>
<p>执行命令行 python2 mlfq.py -j 2 -n 2 -M 0 -m 15 -s 1</p>
<p>程序运行结果如图</p>
<table>
<thead>
<tr>
<th></th>
<th>到达时间</th>
<th>占用 cpu 时间</th>
<th>完成时间</th>
<th>总执行时间</th>
<th>是否调用 io</th>
</tr>
</thead>
<tbody><tr>
<td>job0</td>
<td>0</td>
<td>0-1(位于 Q1)</td>
<td>1</td>
<td>2</td>
<td>否</td>
</tr>
<tr>
<td>job1</td>
<td>0</td>
<td>2-11(位于 Q2)</td>
<td>11</td>
<td>11</td>
<td>否</td>
</tr>
</tbody></table>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111101943355.png" alt="image-20211109210002760"></p>
<p><strong>8.3</strong></p>
<p><strong>将如何配置调度程序参数,像轮转调度程序那样工作?</strong></p>
<p>因为对于同一个队列中的工作,采取轮转的方式调度。所以将队列数设为 1 即可</p>
<p><strong>8.5</strong></p>
<p><strong>给定一个系统,其最高队列中的时间片长度为 10ms,你需要如何频繁地将工作推回到最高优先级级别(带有-B 标志),以保证一个长时间运行(并可能饥饿)的工作得到至少 5%的 CPU?</strong></p>
<p>保证参数 B 小于等于 190,这样至少每隔 200ms 这个长时间运行工作就能被执行 10ms(5%)</p>
<h2 id="第九章"><a href="#第九章" class="headerlink" title="第九章"></a>第九章</h2><h3 id="知识点-4"><a href="#知识点-4" class="headerlink" title="知识点"></a>知识点</h3><h4 id="彩票调度"><a href="#彩票调度" class="headerlink" title="彩票调度"></a><strong>彩票调度</strong></h4><p>彩票数(ticket)代表了进程(或用户或其他)占有某个资源的份额。一个进程拥有的彩票数占总彩票数的百分比,就是它占有资源的份额。</p>
<p><strong>一个简单的例子</strong></p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111111054744.png" alt="image-20211111105425418"></p>
<p><strong>优势</strong></p>
<p>彩票调度最精彩的地方在于利用了随机性<br>随机方法相对于传统的决策方式,至少有 3 点优势。</p>
<p>第一,随机方法常常可以避免奇怪的边角情况<br>第二,随机方法很轻量,几乎不需要记录任何状态。<br>第三,随机方法很快。只要能很快地产生随机数,做出决策就很快</p>
<p><strong>彩票调度机制</strong></p>
<p>1、一种方式是利用彩票货币(ticket currency)的概念。这种方式允许拥有一组彩票的用户以他们喜欢的某种货币,将彩票分给自己的不同工作。之后操作系统再自动将这种货币兑换为正确的全局彩票。</p>
<p>eg:假设用户 A 和用户 B 每人拥有 100 张彩票。用户 A 有两个工作 A1 和 A2,他以自己的货币,给每个工作 500 张彩票(共 1000 张)。用户 B 只运行一个工作,给它 10 张彩票(总共 10 张)。操作系统将进行兑换,将 A1 和 A2 拥有的 A 的货币 500 张,兑换成全局货币 50 张。类似地,兑换给 B1 的 10 张彩票兑换成 100 张。然后会对全局彩票货币(共 200 张)举行抽奖,决定哪个工作运行。</p>
<p>2、彩票转让,一个进程可以临时将自己的彩票交给另一个进程。</p>
<p>3、彩票通胀(ticket inflation)有时也很有用。利用通胀,一个进程可以临时提升或降低自己拥有的彩票数量。</p>
<p><strong>代码实现</strong></p>
<p>要让这个过程更有效率,建议将列表项按照彩票数递减排序</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 // counter: used to track if we've found the winner yet</span><br><span class="line">2 int counter = 0;</span><br><span class="line">3</span><br><span class="line">4 // winner: use some call to a random number generator to</span><br><span class="line">5 // get a value, between 0 and the total # of tickets</span><br><span class="line">6 int winner = getrandom(0, totaltickets);</span><br><span class="line">7</span><br><span class="line">8 // current: use this to walk through the list of jobs</span><br><span class="line">9 node_t *current = head;</span><br><span class="line">10</span><br><span class="line">11 // loop until the sum of ticket values is > the winner</span><br><span class="line">12 while (current) {</span><br><span class="line">13 counter = counter + current->tickets;</span><br><span class="line">14 if (counter > winner)</span><br><span class="line">15 break; // found the winner</span><br><span class="line">16 current = current->next;</span><br><span class="line">17 }</span><br><span class="line">18 // 'current' is the winner: schedule it...</span><br></pre></td></tr></table></figure>
<h4 id="步长调度"><a href="#步长调度" class="headerlink" title="步长调度"></a>步长调度</h4><p>确定性的公平分配算法</p>
<p>步长调度也很简单。系统中的每个工作都有自己的步长,这个值与票数值成反比。当需要进行调度时,选择目前拥有最小行程值的进程,并且在运行之后将该进程的行程值增加一个步长。</p>
<p><strong>一个例子</strong></p>
<p>A、B、C 这 3 个工作的票数分别是 100、50 和 250,我们通过用一个大数分别除以他们的票数来获得每个进程的步长。比如用 10000 除以这些票数值,得到了 3 个进程的步长分别为 100、200 和 40。我们称这个值为每个进程的步长(stride)。每次进程运<br>行后,我们会让它的计数器 [称为行程(pass)值] 增加它的步长,记录它的总体进展。</p>
<p>可以看出,C 运行了 5 次、A 运行了 2 次,B 一次,正好是票数的比例——200、100 和 50。彩票调度算法只能一段时间后,在概率上实现比例,而步长调度算法可以在每个调度周期后做到完全正确。</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111111105771.png" alt="image-20211111110512549"></p>
<p><strong>优势</strong></p>
<p>步长调度能实现一个确定性的公平分配算法。</p>
<p>随机方式可以使得调度程序的实现简单(且大致正确),但偶尔并不能产生正确的比例,尤其在工作运行时间很短的情况下。</p>
<p><strong>劣势</strong></p>
<p>彩票调度有一个步长调度没有的优势——不需要全局状态。因此彩票调度算法能够更合理地处理新加入的进程。</p>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>比例份额调度的两种实现:彩票调度和步长调度。但这两种方法没有作为 CPU 调度程序被广泛使用</p>
<p>原因一:这两种方式都不能很好地适合 I/O[AC97]</p>
<p>原因二:没有解决如何分配彩票的问题</p>
<p>原因三:彩票调度在工作执行时间很短时,平均不公平度非常糟糕。只有当工作执行非常多的时间片时,彩票调度算法才能得到期望的结果。</p>
<p>原因四:步长调度需要全局状态,不能很好的解决新加入的进程</p>
<p>比例份额调度程序只有在这些问题可以相对容易解决的领域更有用(例如容易确定份额比例)。例如在虚拟(virtualized)数据中心中,你可能会希望分配 1/4 的 CPU 周期给 Windows 虚拟机,剩余的给 Linux 系统,比例分配的方式可以更简单高效。</p>
<h3 id="课后习题-4"><a href="#课后习题-4" class="headerlink" title="课后习题"></a>课后习题</h3><p><strong>9.1</strong></p>
<p><strong>计算 3 个工作在随机种子为 1、2 和 3 时的模拟解。</strong></p>
<p>运行命令行 python2 lottery.py -j 3 -s 1,随机种子为 1</p>
<p>可以看到,</p>
<p>时间片 R=1</p>
<p>job0 ,总占用时间:1,彩票数:0-83</p>
<p>job1 ,总占用时间:7,彩票数:84-108</p>
<p>job2 ,总占用时间:2,彩票数:109-152</p>
<p>产生的随机数依次取模得到执行作业的顺序为:</p>
<p>2 0 1 2 2 2 1 1 1 1 1 1</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111111128405.png" alt="image-20211111112849348"></p>
<p>运行命令行 python2 lottery.py -j 3 -s 2,随机种子为 2</p>
<p>同理可得到执行作业的顺序:</p>
<p>2 0 0 2 0 1 0 2 0 0 0 1 0 0 1 2 1 1 1 2 1 1 2</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111111133096.png" alt="image-20211111113351023"></p>
<p>运行命令行 python2 lottery.py -j 3 -s 3,随机种子为 3</p>
<p>同理可得到执行作业的顺序:</p>
<p>1 1 0 1 0 2 2 2 2 2 2</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111111134211.png" alt="image-20211111113449142"></p>
<p><strong>9.2</strong></p>
<p><strong>现在运行两个具体的工作:每个长度为 10,但是一个(工作 0)只有一张彩票,另一个(工作 1)有 100 张(-l 10:1,10:100).彩票数量如此不平衡时会发生什么?在工作 1 完成之前,工作 0 是否会运行?多久?一般来说,这种彩票不平衡对彩票调度的行为有什么影响?</strong></p>
<p>只有一张彩票被调度的可能性非常小,可能会饿死。</p>
<p>在工作 1 完成之前,工作 0 可能会运行。</p>
<p>这种行为可能会导致平均周转和响应时间变得很差</p>
<p><strong>9.3</strong></p>
<p><strong>如果运行两个长度为 100 的工作,都有 100 张彩票(-l 100:100,100:100),调度程序有多不公平?运行一些不同的随机种子来确定(概率上的)答案。不公平性取决于一项工作比另一项工作早完成多少。</strong></p>
<p>分别运行命令行</p>
<p>python2 lottery.py -l 100:100,100:100 -s 1 -c</p>
<p>python2 lottery.py -l 100:100,100:100 -s 25 -c</p>
<p>python2 lottery.py -l 100:100,100:100 -s 50 -c</p>
<p>python2 lottery.py -l 100:100,100:100 -s 100 -q 10 -c(调整时间片为 10)</p>
<p>python2 lottery.py -l 100:100,100:100 -s 100 -q 30 -c(调整时间片为 30)</p>
<p>可以看到随机种子为 1 时 job0 在 192 时完成,job1 在 200 的时候完成</p>
<p>随机种子为 25 时 job1 在 182 时完成,job0 在 200 的时候完成</p>
<p>随机种子为 50 时 job1 在 188 时完成,job0 在 200 的时候完成</p>
<p>公平性都还不错</p>
<p>可以看到当时间片调整为 10 后,job1 在 140 时完成,job0 在 200 的时候完成</p>
<p>当时间片调整为 30 后,job1 在 150 时完成,job0 在 240 的时候完成</p>
<p>公平性较差</p>
<p>一般来说,时间片越小,两个工作完成的时间越接近,公平性越高</p>
<p><img src="https://pic-1310557869.cos.ap-beijing.myqcloud.com/img/202111111143789.png" alt="image-20211111114313728"></p>
<h2 id="第十章——多处理器调度"><a href="#第十章——多处理器调度" class="headerlink" title="第十章——多处理器调度"></a>第十章——多处理器调度</h2><p><strong>多处理器架构</strong></p>
<p><strong>缓存一致性(cache coherence)问题</strong></p>
<p>与单处理器的核心区别在于对硬件缓存(cache)的使用,事实证明,多 CPU 的情况下缓存要复杂得多。例如,假设一个运行在 CPU 1 上的程序从内存地址 A 读取数据。由于不在 CPU 1 的缓存中,所以系统直接访问内存,得到值 D。程序然后修改了地址 A 处的值,只是将它的缓存更新为新值 D’。将数据写回内存比较慢,因此系统(通常)会稍后再做。假设这时操作系统中断了该程序的运行,并将其交给 CPU 2,重新读取地址 A 的数据,由于 CPU 2 的缓存中并没有该数据,所以会直接从内存中读取,得到了旧值 D,而不是正确的值 D’。</p>
<p>基本解决方案:通过监控内存访问,硬件可以保证获得正确的<br>数据,并保证共享内存的唯一性。在基于总线的系统中,一种方式是使用总线窥探(bus snooping)[G83]。每个缓存都通过监听链接所有缓存和内存的总线,来发现内存访问。如果 CPU 发现对它放在缓存中的数据的更新,会作废(invalidate)本地副本(从缓存中移除),<br>或更新(update)它(修改为新值)。回写缓存。</p>
<p><strong>缓存亲和度(cache affinity</strong>)<br>一个进程在某个 CPU 上运行时,会在该 CPU 的缓存中维护许多状态。下次该进程在相同 CPU 上运行时,由于缓存中的数据而执行得更快。相反,在不同的 CPU 上执行,会由于需要重新加载数据而很慢(好在硬件保证的缓存一致性可以保证正确执行)。因此多处理器调度应该考虑到这种缓存亲和性,<strong>并尽可能将进程保持在同一个 CPU 上</strong></p>
<p><strong>单队列调度</strong>(SQMS)</p>
<p>简单地复用单处理器调度的基本架构,将所有需要调度的工作放入一个单独的队列中。</p>
<p>优点:能够从单 CPU 调度程序很简单地发展而来</p>
<p>短板:它的扩展性不好(由于同步开销有限),并且不能很好地保证缓存亲和度。</p>
<p>调度程序的开发者需要在代码中通过加锁(locking)来保证原子性</p>
<p>为了解决缓存亲和性这个问题,大多数 SQMS 调度程序都引入了一些亲和度机制,尽可能让进程在同一个 CPU 上运行。保持一些工作的亲和度的同时,可能需要牺牲其他工作的亲和度来实现负载均衡</p>
<p><strong>多队列调度</strong>MQMS</p>
<p>有些系统使用了多队列的方案,比如每个 CPU 一个队列。我们称之为多队列多处理器调度。基本调度框架包含多个调度队列,每个队列可以使用不同的调度规则</p>
<p>优势:具有可扩展性和缓存亲和性,所有工作都保持在固定的 CPU 上</p>
<p>短板:负载不均衡(每个 CPU 承载的工作不平衡),</p>
<p>解决方案:<strong>工作迁移</strong>,通过工作的跨 CPU 迁移。</p>
<p><strong>工作窃取</strong>,工作量较少的(源)队列不定期地“偷看”其他(目标)队列是不是比自己的工作多。如果目标队列比源队列(显著地)更满,就从目标队列“窃取”一个或多个工作,实现负载均衡。</p>
]]></content>
<categories>
<category>The-Operating-System-Notes</category>
</categories>
<tags>
<tag>operating sysytem</tag>
<tag>linux</tag>
</tags>
</entry>
<entry>
<title>并发性</title>
<url>/2021/03/15/%E5%B9%B6%E5%8F%91%E6%80%A7/</url>
<content><![CDATA[<h1 id="并发"><a href="#并发" class="headerlink" title="并发"></a>并发</h1><h2 id="并发缺陷"><a href="#并发缺陷" class="headerlink" title="并发缺陷"></a>并发缺陷</h2><h3 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h3><p>若系统中存在一组进程,其中每个进程都占用了某种资源,又都在等待已被该组进程中的其他进程占用的资源,这种等待永远不能结束,称为死锁。</p>
<p>死锁产生条件</p>
<p>1、<strong>互斥</strong>:线程对于需要的资源进行互斥的访问(例如一个线程抢到锁)。<br>2、<strong>持有并等待</strong>:线程持有了资源(例如已将持有的锁),同时又在等待其他资源(例如,需要获得的锁)。<br>3、 <strong>非抢占</strong>:线程获得的资源(例如锁),不能被抢占。<br>4、<strong>循环等待</strong>:线程之间存在一个环路,环路上每个线程都额外持有一个资源,而这个资源又是下一个线程要申请的。</p>
<h3 id="活锁"><a href="#活锁" class="headerlink" title="活锁"></a>活锁</h3><p>是指线程 1 可以使用资源,但它很礼貌,让其他线程先使用资源,线程 2 也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。</p>
<h2 id="第-26-章——并发"><a href="#第-26-章——并发" class="headerlink" title="第 26 章——并发"></a>第 26 章——并发</h2><h3 id="知识点"><a href="#知识点" class="headerlink" title="知识点"></a>知识点</h3><h4 id="线程和进程"><a href="#线程和进程" class="headerlink" title="线程和进程"></a>线程和进程</h4><p><strong>线程</strong></p>
<p>经典观点是一个程序只有一个执行点(一个程序计数器,用来存放要执行的指令),但多线程(multi-threaded)程序会有多个执行点(多个程序计数器,每个都用于取指令和执行)。换一个角度来看,每个线程类似于独立的进程</p>
<p><strong>区别:</strong></p>
<p>每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,<strong>同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC)</strong>,线程之间切换的开销小。</p>
<ol>
<li>线程之间它们共享地址空间,从而能够访问相同的数据。</li>
<li>线程上下文切换地址空间保持不变(即不需要切换当前使用的页表)。</li>
<li>状态保存到线程控制块(TCB)</li>
<li>在简单的传统进程地址空间模型中,只有一个栈,通常位于地址空间的底部,每个线程都有一个栈</li>
</ol>
<p><img src="https://s2.loli.net/2022/01/09/cVOeF4mlSv5soby.png" alt="image-20211111221705381"></p>
<p><strong>相同点:</strong></p>
<ol>
<li>线程有一个程序计数器(PC),记录程序从哪里获取指令。每个线程有自己的一组用于计算的寄存器。</li>
<li>线程之间的上下文切换类似于进程间的上下文切换。</li>
</ol>
<h4 id="线程创建"><a href="#线程创建" class="headerlink" title="线程创建"></a>线程创建</h4><p>主程序创建了两个线程,分别执行函数 mythread(),但是传入不同的参数(字符串类型的 A 或者 B)。一旦线程创建,可能会立即运行(取决于调度程序),或者处于就绪状态,等待执行。创建了两个线程(T1 和 T2)后,主程序调用 pthread_join(),等待特定线程完成。</p>
<p>代码实现</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#include <stdio.h></span><br><span class="line">#include <assert.h></span><br><span class="line">#include <pthread.h></span><br><span class="line"></span><br><span class="line">void *mythread(void *arg)</span><br><span class="line">{</span><br><span class="line"> printf("%s\n", (char *) arg); return NULL;</span><br><span class="line">}</span><br><span class="line">int main(int argc, char *argv[]) {</span><br><span class="line"></span><br><span class="line">pthread_t p1, p2;</span><br><span class="line">int rc;</span><br><span class="line">printf("main: begin\n");</span><br><span class="line">rc = pthread_create(&p1, NULL, mythread, "A");</span><br><span class="line">assert(rc == 0);</span><br><span class="line">rc = pthread_create(&p2, NULL, mythread, "B");</span><br><span class="line">assert(rc == 0);</span><br><span class="line">// join waits for the threads to finish</span><br><span class="line">rc = pthread_join(p1, NULL);</span><br><span class="line">assert(rc == 0);</span><br><span class="line">rc = pthread_join(p2, NULL);</span><br><span class="line">assert(rc == 0);</span><br><span class="line">printf("main: end\n");</span><br><span class="line">return 0;</span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h4 id="共享数据带来的麻烦"><a href="#共享数据带来的麻烦" class="headerlink" title="共享数据带来的麻烦"></a>共享数据带来的麻烦</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#include <stdio.h></span><br><span class="line">#include <pthread.h></span><br><span class="line">#include "mythreads.h"</span><br><span class="line"></span><br><span class="line">static volatile int counter = 0;</span><br><span class="line"></span><br><span class="line">//</span><br><span class="line">// mythread()</span><br><span class="line">//</span><br><span class="line">// Simply adds 1 to counter repeatedly, in a loop</span><br><span class="line">// No, this is not how you would add 10,000,000 to</span><br><span class="line">// a counter, but it shows the problem nicely.</span><br><span class="line">// void *</span><br><span class="line">mythread(void *arg)</span><br><span class="line">{</span><br><span class="line">printf("%s: begin\n", (char *) arg);</span><br><span class="line">int i;</span><br><span class="line">for (i = 0; i < 1e7; i++)</span><br><span class="line">{</span><br><span class="line"> counter = counter + 1;</span><br><span class="line">}</span><br><span class="line">printf("%s: done\n", (char *) arg); return NULL;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// Just launches two threads (pthread_create)</span><br><span class="line">// and then waits for them (pthread_join)</span><br><span class="line">//</span><br><span class="line">int</span><br><span class="line">main(int argc, char *argv[])</span><br><span class="line">{</span><br><span class="line">pthread_t p1, p2;</span><br><span class="line">printf("main: begin (counter = %d)\n", counter); Pthread_create(&p1, NULL, mythread, "A");</span><br><span class="line">Pthread_create(&p2, NULL, mythread, "B");</span><br><span class="line"></span><br><span class="line">// join waits for the threads to finish</span><br><span class="line">Pthread_join(p1, NULL);</span><br><span class="line">Pthread_join(p2, NULL);</span><br><span class="line">printf("main: done with both (counter = %d)\n", counter); return 0;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>预期的最终结果是:20000000,但是每次运行的结果都不太符合预期且各不相同</p>
<h4 id="核心问题:不可控的调度"><a href="#核心问题:不可控的调度" class="headerlink" title="核心问题:不可控的调度"></a>核心问题:不可控的调度</h4><p>两个线程访问的是共享数据,并试图修改共享数据,也就是进入了临界区。此时如何发生上下文切换,多个线程处于竞争状态,可能会造成不可知的错误。</p>
<p>我们真正想要的代码就是所谓的互斥(mutual exclusion)。这个属性保证了如果一个线程在临界区内执行,其他线程将被阻止进入临界区。</p>
<p><img src="https://s2.loli.net/2022/01/09/Gysa9uLkzYnShJp.png" alt="image-20211111225421897"></p>
<h4 id="原子操作"><a href="#原子操作" class="headerlink" title="原子操作"></a>原子操作</h4><p><em>原子操作是构建计算机系统的最强大的基础技术之一,从计算机体系结构到并行代码(我们在这里研究的内容)、文件系统(我们将很快研究)、数据库管理系统,甚至分布式系统[L+93]。将一系列动作原子化(atomic)背后的想法可以简单用一个短语表达:“全部或没有”。看上去,要么你希望组合在一起的所有活动都发生了,要么它们都没有发生。不会看到中间状态。有时,将许多行</em><br><em>为组合为单个原子动作称为事务(transaction),这是一个在数据库和事务处理世界中非常详细地发展的概念</em></p>
<h2 id="第-28-章-锁"><a href="#第-28-章-锁" class="headerlink" title="第 28 章 锁"></a>第 28 章 锁</h2><p>对于<a href="https://so.csdn.net/so/search?q=%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B">并发编程</a>的一个最基本的问题:程序员希望原子式执行一系列指令,但由于单处理器上的中断(或多线程在多处理器上并发执行),这变得不可实现,为了解决这个问题,<strong>在源代码中加锁,放在临界区周围,保证临界区能像单条原子指令一样执行</strong></p>
<h2 id="第-30-章-条件变量"><a href="#第-30-章-条件变量" class="headerlink" title="第 30 章 条件变量"></a>第 30 章 条件变量</h2><h3 id="知识点-1"><a href="#知识点-1" class="headerlink" title="知识点"></a><strong>知识点</strong></h3><p>。锁并不是并发程序设计所需的唯一原语。在很多情况下,线程需要检查某一条件满足之后,才会继续运行。例如,父线程需要检查子线程是否执行完毕 [这常被称为 join()]。</p>
<p><strong>方案一:原地自旋</strong></p>
<p>效率低下,有些情况设置是错误的</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 volatile int done = 0;</span><br><span class="line">2</span><br><span class="line">3 void *child(void *arg) {</span><br><span class="line">4 printf("child\n");</span><br><span class="line">5 done = 1;</span><br><span class="line">6 return NULL;</span><br><span class="line">7 }</span><br><span class="line">8</span><br><span class="line">9 int main(int argc, char *argv[]) {</span><br><span class="line">10 printf("parent: begin\n");</span><br><span class="line">11 pthread_t c;</span><br><span class="line">12 Pthread_create(&c, NULL, child, NULL); // create child</span><br><span class="line">13 while (done == 0)</span><br><span class="line">14 ; // spin</span><br><span class="line">15 printf("parent: end\n");</span><br><span class="line">16 return 0;</span><br><span class="line">17 }</span><br></pre></td></tr></table></figure>
<p><strong>方案二:</strong></p>
<p>可以使用条件变量(condition variable),来等待一个条件变成真。</p>
<p>条件变量有两种相关操作:wait()和 signal()。线程要睡<br>眠的时候,调用 wait()。当线程想唤醒等待在某个条件变量上的睡眠线程时,调用 signal()。具体来说,POSIX 调用如图所示。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m);</span><br><span class="line">pthread_cond_signal(pthread_cond_t *c);</span><br><span class="line">1 int done = 0;</span><br><span class="line">2 pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;</span><br><span class="line">3 pthread_cond_t c = PTHREAD_COND_INITIALIZER;</span><br><span class="line">4</span><br><span class="line">5 void thr_exit() {</span><br><span class="line">6 Pthread_mutex_lock(&m);</span><br><span class="line">7 done = 1;</span><br><span class="line">8 Pthread_cond_signal(&c);</span><br><span class="line">9 Pthread_mutex_unlock(&m);</span><br><span class="line">10 }</span><br><span class="line">11</span><br><span class="line">12 void *child(void *arg) {</span><br><span class="line">13 printf("child\n");</span><br><span class="line">14 thr_exit();</span><br><span class="line">15 return NULL;</span><br><span class="line">16 }</span><br><span class="line">30.1 定义和程序 251</span><br><span class="line">17</span><br><span class="line">18 void thr_join() {</span><br><span class="line">19 Pthread_mutex_lock(&m);</span><br><span class="line">20 while (done == 0)</span><br><span class="line">21 Pthread_cond_wait(&c, &m);</span><br><span class="line">22 Pthread_mutex_unlock(&m);</span><br><span class="line">23 }</span><br><span class="line">24</span><br><span class="line">25 int main(int argc, char *argv[]) {</span><br><span class="line">26 printf("parent: begin\n");</span><br><span class="line">27 pthread_t p;</span><br><span class="line">28 Pthread_create(&p, NULL, child, NULL);</span><br><span class="line">29 thr_join();</span><br><span class="line">30 printf("parent: end\n");</span><br><span class="line">31 return 0;</span><br><span class="line">32 }</span><br></pre></td></tr></table></figure>
<h4 id="生产者-x2F-消费者问题"><a href="#生产者-x2F-消费者问题" class="headerlink" title="生产者/消费者问题"></a><strong>生产者/消费者问题</strong></h4><p>生产者/消费者问题,也叫作有界缓冲区(bounded buffer)问题。假设有一个或多个生产者线程和一个或多个消费者线程。生产者把生成的数据项放入缓冲区;消费者从缓冲区取走数据项,以某种方式消费。</p>
<h5 id="有问题的方案一"><a href="#有问题的方案一" class="headerlink" title="有问题的方案一"></a><strong>有问题的方案一</strong></h5><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 cond_t cond;</span><br><span class="line">2 mutex_t mutex;</span><br><span class="line">3‘</span><br><span class="line">4 void *producer(void *arg) {</span><br><span class="line">5 int i;</span><br><span class="line">6 for (i = 0; i < loops; i++) {</span><br><span class="line">7 Pthread_mutex_lock(&mutex); // p1</span><br><span class="line">8 if (count == 1) // p2</span><br><span class="line">9 Pthread_cond_wait(&cond, &mutex); // p3</span><br><span class="line">10 put(i); // p4</span><br><span class="line">11 Pthread_cond_signal(&cond); // p5</span><br><span class="line">12 Pthread_mutex_unlock(&mutex); // p6</span><br><span class="line">13 }</span><br><span class="line">14 }</span><br><span class="line">15</span><br><span class="line">16 void *consumer(void *arg) {</span><br><span class="line">17 int i;</span><br><span class="line">18 for (i = 0; i < loops; i++) {</span><br><span class="line">19 Pthread_mutex_lock(&mutex); // c1</span><br><span class="line">20 if (count == 0) // c2</span><br><span class="line">21 Pthread_cond_wait(&cond, &mutex); // c3</span><br><span class="line">22 int tmp = get(); // c4</span><br><span class="line">23 Pthread_cond_signal(&cond); // c5</span><br><span class="line">24 Pthread_mutex_unlock(&mutex); // c6</span><br><span class="line">30.2 生产者/消费者(有界缓冲区)问题 255</span><br><span class="line">25 printf("%d\n", tmp);</span><br><span class="line">26 }</span><br><span class="line">27</span><br></pre></td></tr></table></figure>
<p>出问题的例子:</p>
<p><img src="https://s2.loli.net/2022/01/09/oGh1SzMmxZ7VpnB.png" alt="image-20211111145947333"></p>
<p><img src="https://s2.loli.net/2022/01/09/iklyu4nr6AgZbQ5.png" alt="image-20211111150007868"></p>
<p>问题产生的原因很简单:在 T c1 被生产者唤醒后,但在它运行之前,缓冲区的状态改变了(由于 T c2 )。发信号给线程只是唤醒它们,暗示状态发生了变化(在这个例子中,就是值已被放入缓冲区),但并不会保证在它运行之前状态一直是期望的情况。</p>
<h5 id="有问题的方案二"><a href="#有问题的方案二" class="headerlink" title="有问题的方案二"></a><strong>有问题的方案二</strong></h5><p>使用 While 语句替代 If</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 cond_t cond;</span><br><span class="line">2 mutex_t mutex;</span><br><span class="line">3</span><br><span class="line">4 void *producer(void *arg) {</span><br><span class="line">5 int i;</span><br><span class="line">6 for (i = 0; i < loops; i++) {</span><br><span class="line">7 Pthread_mutex_lock(&mutex); // p1</span><br><span class="line">8 while (count == 1) // p2</span><br><span class="line">9 Pthread_cond_wait(&cond, &mutex); // p3</span><br><span class="line">10 put(i); // p4</span><br><span class="line">11 Pthread_cond_signal(&cond); // p5</span><br><span class="line">12 Pthread_mutex_unlock(&mutex); // p6</span><br><span class="line">13 }</span><br><span class="line">14 }</span><br><span class="line">15</span><br><span class="line">16 void *consumer(void *arg) {</span><br><span class="line">17 int i;</span><br><span class="line">18 for (i = 0; i < loops; i++) {</span><br><span class="line">19 Pthread_mutex_lock(&mutex); // c1</span><br><span class="line">20 while (count == 0) // c2</span><br><span class="line">21 Pthread_cond_wait(&cond, &mutex); // c3</span><br><span class="line">22 int tmp = get(); // c4</span><br><span class="line">23 Pthread_cond_signal(&cond); // c5</span><br><span class="line">24 Pthread_mutex_unlock(&mutex); // c6</span><br><span class="line">25 printf("%d\n", tmp);</span><br><span class="line">26 }</span><br><span class="line">27 }</span><br></pre></td></tr></table></figure>
<p>我们要记住一条关于条件变量的简单规则:总是使用 while 循环(always use while loop)。虽然有时候不需要重新检查条件,但这样做总是安全的。</p>
<p><strong>存在的问题:</strong></p>
<p>因为消费者已经清空了缓冲区,很显然,应该唤醒生产者。但是,如果它唤醒了 T c2 (这绝对是可能的,取决于等待队列是如何管理的),问题就出现了。具体来说,消费者 T c2 会醒过来,发现队列为空(c2),又继续回去睡眠(c3)。生产者 T p 刚才在缓冲区中放了一个值,现在在睡眠。另一个消费者线程 T c1 也回去睡眠了。3 个线程都在睡眠,显然是一个缺陷。</p>
<p><img src="https://s2.loli.net/2022/01/09/isgFNlt23zynRKq.png" alt="image-20211111150848091"></p>
<p><img src="https://s2.loli.net/2022/01/09/uHFybaIhgnBNDit.png" alt="image-20211111150906666"></p>
<p>信号显然需要,但必须更有指向性。消费者不应该唤醒消费者,而应该只唤醒生产者,反之亦然。</p>
<p><strong>方案三:单值缓冲区的生产者/消费者方案</strong></p>
<p>生产者线程等待条件变量 empty,发信号给变量 fill。相应地,消费者线程等待 fill,发信号给 empty。这样做,从设计上避免了上述第二个问题:消费者再也不会唤醒消费者,生产者也不会唤醒生产者。</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 cond_t empty, fill;</span><br><span class="line">2 mutex_t mutex;</span><br><span class="line">3</span><br><span class="line">4 void *producer(void *arg) {</span><br><span class="line">5 int i;</span><br><span class="line">6 for (i = 0; i < loops; i++) {</span><br><span class="line">7 Pthread_mutex_lock(&mutex);</span><br><span class="line">8 while (count == 1)</span><br><span class="line">9 Pthread_cond_wait(&empty, &mutex);</span><br><span class="line">10 put(i);</span><br><span class="line">11 Pthread_cond_signal(&fill);</span><br><span class="line">12 Pthread_mutex_unlock(&mutex);</span><br><span class="line">13 }</span><br><span class="line">14 }</span><br><span class="line">15</span><br><span class="line">16 void *consumer(void *arg) {</span><br><span class="line">17 int i;</span><br><span class="line">18 for (i = 0; i < loops; i++) {</span><br><span class="line">19 Pthread_mutex_lock(&mutex);</span><br><span class="line">20 while (count == 0)</span><br><span class="line">21 Pthread_cond_wait(&fill, &mutex);</span><br><span class="line">22 int tmp = get();</span><br><span class="line">23 Pthread_cond_signal(&empty);</span><br><span class="line">24 Pthread_mutex_unlock(&mutex);</span><br><span class="line">25 printf("%d\n", tmp);</span><br><span class="line">26 }</span><br><span class="line">27 }</span><br></pre></td></tr></table></figure>
<h5 id="最终方案"><a href="#最终方案" class="headerlink" title="最终方案"></a><strong>最终方案</strong></h5><p><img src="https://s2.loli.net/2022/01/07/GHbpioTwRqa9tOM.png" alt="image-20220107140850903"></p>
<p><img src="https://s2.loli.net/2022/01/07/z9F6rOUKJL8wli1.png" alt="image-20220107140841597"></p>
<p>llll</p>
<h2 id="第-31-章:信号量"><a href="#第-31-章:信号量" class="headerlink" title="第 31 章:信号量"></a>第 31 章:信号量</h2><h3 id="信号量定义"><a href="#信号量定义" class="headerlink" title="信号量定义"></a>信号量定义</h3><p>信号量作为锁和条件变量</p>
<p>是有一个整数值的对象,可以用两个函数来操作它</p>
<p>**sem_wait()**要么立刻返回(调用 sem_wait()时,信号量的值大于等于 1),要么会让调用线程挂起,直到之后的一个 post 操作。当然,也可能多个调用线程都调用 sem_wait(),因此都在队列中等待被唤醒。</p>
<p><strong>sem_post()</strong> sem_post()并没有等待某些条件满足。它直接增加信号量的值,如果有等待线程,唤醒其中一个。当信号量的值为负数时,这个值就是等待线程的个数</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">sem_wait</span><span class="params">(<span class="type">sem_t</span> *s)</span> {</span><br><span class="line">decrement the value of semaphore s by one</span><br><span class="line">wait <span class="keyword">if</span> value of semaphore s is negative</span><br><span class="line">}</span><br><span class="line"> <span class="type">int</span> <span class="title function_">sem_post</span><span class="params">(<span class="type">sem_t</span> *s)</span> {</span><br><span class="line">increment the value of semaphore s by one</span><br><span class="line"><span class="keyword">if</span> there are one or more threads waiting, wake one</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="二值信号量(锁)"><a href="#二值信号量(锁)" class="headerlink" title="二值信号量(锁)"></a>二值信号量(锁)</h3><p>用信号量作为锁。信号量初始值为 1</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">sem_t m;</span><br><span class="line">sem_init(&m, 0, X); // initialize semaphore to X; what should X be?</span><br><span class="line"></span><br><span class="line">sem_wait(&m);</span><br><span class="line">// critical section here</span><br><span class="line">sem_post(&m);</span><br></pre></td></tr></table></figure>
<p>考虑两种场景</p>
<p>1.一个单线程,调用了 sem_wait(),它把信号量的值减为 0。然后,它只会在值小于 0 时等待。因为值是 0,调用线程从函数返回并继续,线程 0 现在可以自由进入临界区。线程 0 在临界区中,如果没有其他线程尝试获取锁,当它调用 sem_post()时,会将信号量重置为 1(因为没有等待线程,不会唤醒其他线程)。</p>
<p><img src="https://gitee.com/nnilk/cloudimage/raw/master/img/202111171023858.png" alt="image-20211117102303687"></p>
<h3 id="信号量用作条件变量"><a href="#信号量用作条件变量" class="headerlink" title="信号量用作条件变量"></a>信号量用作条件变量</h3><p>信号量初始值应为 0.</p>
<p>有两种情况需要考虑</p>
<p>第一种,父线程创建了子线程,但是子线程并没有运行。这种情况下(见表 31.3),父线程调用 sem_wait()会先于子线程调用 sem_post()。我们希望父线程等待子线程运行。为此,唯一的办法是让信号量的值不大于 0。因此,0 为初值。父线程运行,将信号量减为 −1,然后睡眠等待;子线程运行的时候,调用 sem_post(),信号量增加为 0,唤醒父线程,父线程然后从 sem_wait()返回,完成该程序。</p>
<p>第二种情况是子线程在父线程调用 sem_wait()之前就运行结束(见表 31.4)。在这种情况下,子线程会先调用 sem_post(),将信号量从 0 增加到 1。然后当父线程有机会运行时,会调用 sem_wait(),发现信号量的值为 1。于是父线程将信号量从 1 减为 0,没有等待,直接从<br>sem_wait()返回,也达到了预期效果。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span> <span class="type">sem_t</span> s;</span><br><span class="line"><span class="number">2</span></span><br><span class="line"><span class="number">3</span> <span class="type">void</span> *<span class="title function_">child</span><span class="params">(<span class="type">void</span> *arg)</span> {</span><br><span class="line"><span class="number">5</span> <span class="built_in">printf</span>(<span class="string">"child\n"</span>);</span><br><span class="line"><span class="number">6</span> sem_post(&s); <span class="comment">// signal here: child is done</span></span><br><span class="line"><span class="number">7</span> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"><span class="number">8</span> }</span><br><span class="line"><span class="number">9</span></span><br><span class="line"><span class="number">10</span> <span class="type">int</span></span><br><span class="line"><span class="number">11</span> main(<span class="type">int</span> argc, <span class="type">char</span> *argv[]) {</span><br><span class="line"><span class="number">12</span> sem_init(&s, <span class="number">0</span>, X); <span class="comment">// what should X be?</span></span><br><span class="line"><span class="number">13</span> <span class="built_in">printf</span>(<span class="string">"parent: begin\n"</span>);</span><br><span class="line"><span class="number">14</span> <span class="type">pthread_t</span> c;</span><br><span class="line"><span class="number">15</span> Pthread_create(c, <span class="literal">NULL</span>, child, <span class="literal">NULL</span>);</span><br><span class="line"><span class="number">16</span> sem_wait(&s); <span class="comment">// wait here for child</span></span><br><span class="line"><span class="number">17</span> <span class="built_in">printf</span>(<span class="string">"parent: end\n"</span>);</span><br><span class="line"><span class="number">18</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="number">19</span> }</span><br></pre></td></tr></table></figure>
<h3 id="生产者-x2F-消费者问题-1"><a href="#生产者-x2F-消费者问题-1" class="headerlink" title="生产者/消费者问题"></a>生产者/消费者问题</h3><h4 id="初次尝试"><a href="#初次尝试" class="headerlink" title="初次尝试"></a>初次尝试</h4><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span> <span class="type">sem_t</span> empty; <span class="comment">//生产者的等待条件判断变量,相当于缓冲区的剩余容量,被初始化为MAX</span></span><br><span class="line"><span class="number">2</span> <span class="type">sem_t</span> full; <span class="comment">//消费者的等待条件判断变量,相当于缓冲区的数据数量,初始值为0</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="number">4</span> <span class="type">void</span> *<span class="title function_">producer</span><span class="params">(<span class="type">void</span> *arg)</span> {</span><br><span class="line"><span class="number">5</span> <span class="type">int</span> i;</span><br><span class="line"><span class="number">6</span> <span class="keyword">for</span> (i = <span class="number">0</span>; i < loops; i++) {</span><br><span class="line"><span class="number">7</span> sem_wait(&empty); <span class="comment">//line P1 //初始为MAX,函数返回,执行放入</span></span><br><span class="line"><span class="number">8</span> put(i); <span class="comment">// line P2</span></span><br><span class="line"><span class="number">9</span> sem_post(&full); <span class="comment">// line P3 // full+1</span></span><br><span class="line"><span class="number">10</span> }</span><br><span class="line"><span class="number">11</span> }</span><br><span class="line"><span class="number">12</span></span><br><span class="line"><span class="number">13</span> <span class="type">void</span> *<span class="title function_">consumer</span><span class="params">(<span class="type">void</span> *arg)</span> {</span><br><span class="line"><span class="number">14</span> <span class="type">int</span> i, tmp = <span class="number">0</span>;</span><br><span class="line"><span class="number">15</span> <span class="keyword">while</span> (tmp != <span class="number">-1</span>) {</span><br><span class="line"><span class="number">16</span> sem_wait(&full); <span class="comment">// line C1 //full初始值为0,缓冲区没有数据,wait函数将full-1,进入等待状态</span></span><br><span class="line"><span class="number">17</span> tmp = get(); <span class="comment">// line C2</span></span><br><span class="line"><span class="number">18</span> sem_post(&empty); <span class="comment">// line C3</span></span><br><span class="line"><span class="number">19</span> <span class="built_in">printf</span>(<span class="string">"%d\n"</span>, tmp);</span><br><span class="line"><span class="number">20</span> }</span><br><span class="line"></span><br><span class="line"><span class="number">21</span> }</span><br><span class="line"><span class="number">22</span></span><br><span class="line"><span class="number">23</span> <span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> {</span><br><span class="line"><span class="number">24</span> <span class="comment">// ...</span></span><br><span class="line"><span class="number">25</span> sem_init(&empty, <span class="number">0</span>, MAX); <span class="comment">// MAX buffers are empty to begin with...</span></span><br><span class="line"><span class="number">26</span> sem_init(&full, <span class="number">0</span>, <span class="number">0</span>); <span class="comment">// ... and 0 are full</span></span><br><span class="line"><span class="number">27</span> <span class="comment">// ...</span></span><br><span class="line"><span class="number">28</span> }</span><br></pre></td></tr></table></figure>
<p><strong>存在的问题:竞态条件</strong></p>
<p>我们现在假设 MAX 大于 1(比如 MAX=10)。对于这个例子,假定有多个生产者,多<br>个消费者。现在就有问题了:竞态条件。假设两个生产者(Pa 和 Pb)几乎同时调用 put()。当 Pa 先运行,在 f1 行先加入第一条数据(fill=0),假设 Pa 在将 fill 计数器更新为 1 之前被中断,Pb 开始运行,也在 f1 行给缓冲区的 0 位置加入一条数据,这意味着那里的老数据被覆盖!</p>
<h4 id="解决竞态-增加互斥"><a href="#解决竞态-增加互斥" class="headerlink" title="解决竞态:增加互斥"></a>解决竞态:增加互斥</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 sem_t empty;</span><br><span class="line">2 sem_t full;</span><br><span class="line">3 sem_t mutex;</span><br><span class="line">4</span><br><span class="line">5 void *producer(void *arg) {</span><br><span class="line">6 int i;</span><br><span class="line">7 for (i = 0; i < loops; i++) {</span><br><span class="line">8 sem_wait(&mutex); // line p0 (NEW LINE)</span><br><span class="line">9 sem_wait(&empty); // line p1</span><br><span class="line">10 put(i); // line p2</span><br><span class="line">11 sem_post(&full); // line p3</span><br><span class="line">12 sem_post(&mutex); // line p4 (NEW LINE)</span><br><span class="line">13 }</span><br><span class="line">14 }</span><br><span class="line">15</span><br><span class="line">16 void *consumer(void *arg) {</span><br><span class="line">17 int i;</span><br><span class="line">18 for (i = 0; i < loops; i++) {</span><br><span class="line">19 sem_wait(&mutex); // line c0 (NEW LINE)</span><br><span class="line">20 sem_wait(&full); // line c1</span><br><span class="line">21 int tmp = get(); // line c2</span><br><span class="line">22 sem_post(&empty); // line c3</span><br><span class="line">23 sem_post(&mutex); // line c4 (NEW LINE)</span><br><span class="line">24 printf("%d\n", tmp);</span><br><span class="line">25 }</span><br><span class="line">26 }</span><br><span class="line">27</span><br><span class="line">28 int main(int argc, char *argv[]) {</span><br><span class="line">29 // ...</span><br><span class="line">30 sem_init(&empty, 0, MAX); // MAX buffers are empty to begin with...</span><br><span class="line">31 sem_init(&full, 0, 0); // ... and 0 are full</span><br><span class="line">32 sem_init(&mutex, 0, 1); // mutex=1 because it is a lock (NEW LINE)</span><br><span class="line">33 // ...</span><br><span class="line">34 }</span><br></pre></td></tr></table></figure>
<h4 id="存在的问题-死锁"><a href="#存在的问题-死锁" class="headerlink" title="存在的问题:死锁"></a>存在的问题:死锁</h4><p>假设有两个线程,一个生产者和一个消费者。消费者首先运行,获得锁(c0 行),然后对 full 信号量执行 sem_wait() (c1 行)。因为还没有数据,所以消费者阻塞,让出 CPU。但是,重要的是,此时消费者仍然持有锁。然后生产者运行。假如生产者能够运行,它就能生产数据并唤醒消费者线程。遗憾的是,它首先对二值互斥信号量调用 sem_wait()(p0 行)。锁已经被持有,因此生产者也被卡住。这里出现了一个循环等待。消费者持有互斥量,等待在 full 信号量上。生产者可以发送 full 信号,却在等待互斥量。因此,生产者和消费者互相等待对方——典型的死锁。</p>
<h4 id="解决死锁-减少锁的作用域"><a href="#解决死锁-减少锁的作用域" class="headerlink" title="解决死锁:减少锁的作用域"></a>解决死锁:减少锁的作用域</h4><p><img src="https://s2.loli.net/2022/01/07/nPY936j5tzg8GKk.png" alt="image-20220107141050654"></p>
<h3 id="读者—写者锁"><a href="#读者—写者锁" class="headerlink" title="读者—写者锁"></a>读者—写者锁</h3><p>不同的数据结构访问可能需要不同类型的锁。</p>
<p>如果某个线程要更新数据结构,需要调用 rwlock_acquire_lock()获得写锁,调用 rwlock_release_writelock()释放锁。内部通过一个 writelock 的信号量保证只有一个写者能获得锁进入临界区,从而更新数据结构。</p>
<p>但有一些缺陷,尤其是公平性。读者很容易饿死写者。存在复杂一些的解决方案,也许你可以想到更好的实现?提示:有写者等待时,如何能够避更多的读者进入并持有锁。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span> <span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">rwlock_t</span> {</span></span><br><span class="line"><span class="number">2</span> <span class="type">sem_t</span> lock; <span class="comment">// binary semaphore (basic lock)</span></span><br><span class="line"><span class="number">3</span> <span class="type">sem_t</span> writelock; <span class="comment">// used to allow ONE writer or MANY readers</span></span><br><span class="line"><span class="number">4</span> <span class="type">int</span> readers; <span class="comment">// count of readers reading in critical section</span></span><br><span class="line"><span class="number">5</span> } <span class="type">rwlock_t</span>;</span><br><span class="line"><span class="number">6</span></span><br><span class="line"><span class="number">7</span> <span class="type">void</span> <span class="title function_">rwlock_init</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">8</span> rw->readers = <span class="number">0</span>;</span><br><span class="line"><span class="number">9</span> sem_init(&rw->lock, <span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line"><span class="number">10</span> sem_init(&rw->writelock, <span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line"><span class="number">11</span> }</span><br><span class="line"><span class="number">12</span></span><br><span class="line"><span class="number">13</span> <span class="type">void</span> <span class="title function_">rwlock_acquire_readlock</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">14</span> sem_wait(&rw->lock);</span><br><span class="line"><span class="number">15</span> rw->readers++;</span><br><span class="line"><span class="number">16</span> <span class="keyword">if</span> (rw->readers == <span class="number">1</span>)</span><br><span class="line"><span class="number">17</span> sem_wait(&rw->writelock); <span class="comment">// first reader acquires writelock</span></span><br><span class="line"><span class="number">18</span> sem_post(&rw->lock);</span><br><span class="line"><span class="number">19</span> }</span><br><span class="line"><span class="number">20</span></span><br><span class="line"><span class="number">21</span> <span class="type">void</span> <span class="title function_">rwlock_release_readlock</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">22</span> sem_wait(&rw->lock);</span><br><span class="line"><span class="number">23</span> rw->readers--;</span><br><span class="line"><span class="number">24</span> <span class="keyword">if</span> (rw->readers == <span class="number">0</span>)</span><br><span class="line"><span class="number">25</span> sem_post(&rw->writelock); <span class="comment">// last reader releases writelock</span></span><br><span class="line"><span class="number">26</span> sem_post(&rw->lock);</span><br><span class="line"><span class="number">27</span> }</span><br><span class="line"><span class="number">28</span></span><br><span class="line"><span class="number">29</span> <span class="type">void</span> <span class="title function_">rwlock_acquire_writelock</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">30</span> sem_wait(&rw->writelock);</span><br><span class="line"><span class="number">31</span> }</span><br><span class="line"><span class="number">32</span></span><br><span class="line"><span class="number">33</span> <span class="type">void</span> <span class="title function_">rwlock_release_writelock</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">34</span> sem_post(&rw->writelock);</span><br><span class="line"><span class="number">35</span> }</span><br></pre></td></tr></table></figure>
<h3 id="哲学家就餐问题"><a href="#哲学家就餐问题" class="headerlink" title="哲学家就餐问题"></a>哲学家就餐问题</h3><p>假定有 5 位“哲学家”围着一个圆桌。每两位哲学家之间有一把餐叉(一共 5 把)。哲学家有时要思考一会,不需要餐叉;有时又要就餐。而一位哲学家只有同时拿到了左手边和右手边的两把餐叉,才能吃到东西。关于餐叉的竞争以及随之而来的同步问题,就是我们在并发编程中研<br>究它的原因。</p>
<p><img src="https://gitee.com/nnilk/cloudimage/raw/master/img/202111251057977.png" alt="image-20211117112437466"></p>
<h4 id="有问题的解决方案"><a href="#有问题的解决方案" class="headerlink" title="有问题的解决方案"></a>有问题的解决方案</h4><p>如果哲学家 p 希望用左手边的叉子,他们就调用 left(p)。类似地,右手边的叉子就用<br>right(p)。模运算解决了最后一个哲学家(p = 4)右手边叉子的编号问题,就是餐叉 0。</p>
<p>为了拿到餐叉,我们依次获取每把餐叉的锁——先是左手边的,然后是右手边的。结束就餐时,释放掉锁</p>
<p>**问题:死锁 ** 假设每个哲学家都拿到了左手边的餐叉,他们每个都会阻塞住,并且一直等待另一个餐叉。具体来说,哲学家 0 拿到了餐叉 0,哲学家 1 拿到了餐叉 1,哲学家 2 拿到餐叉 2,哲学家 3 拿到餐叉 3,哲学家 4 拿到餐叉 4。所有的餐叉都被占有了,所有的哲学家都阻塞着,并且等待另一个哲学家占有的餐叉</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 void getforks() {</span><br><span class="line">2 sem_wait(forks[left(p)]);</span><br><span class="line">3 sem_wait(forks[right(p)]);</span><br><span class="line">4 }</span><br><span class="line">5</span><br><span class="line">6 void putforks() {</span><br><span class="line">7 sem_post(forks[left(p)]);</span><br><span class="line">8 sem_post(forks[right(p)]);</span><br><span class="line">9 }</span><br></pre></td></tr></table></figure>
<h4 id="解决方案-破除依赖"><a href="#解决方案-破除依赖" class="headerlink" title="解决方案:破除依赖"></a>解决方案:破除依赖</h4><p>改变某位哲学家的用餐顺序,因为最后一个哲学家会尝试先拿右手边的餐叉,然后拿左手边,所以不会出现每个哲学家都拿着一个餐叉,卡住等待另一个的情况,等待循环被打破了</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 void getforks() {</span><br><span class="line">2 if (p == 4) {</span><br><span class="line">3 sem_wait(forks[right(p)]);</span><br><span class="line">4 sem_wait(forks[left(p)]);</span><br><span class="line">5 } else {</span><br><span class="line">6 sem_wait(forks[left(p)]);</span><br><span class="line">7 sem_wait(forks[right(p)]);</span><br><span class="line">8 }</span><br><span class="line">9 }</span><br></pre></td></tr></table></figure>
<h3 id="如何实现信号量"><a href="#如何实现信号量" class="headerlink" title="如何实现信号量"></a>如何实现信号量</h3><p>我们用底层的同步原语(锁和条件变量),来实现自己的信号量,名字叫作 Zemaphore。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"> <span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">Zem_t</span></span></span><br><span class="line"><span class="class"> {</span> <span class="type">int</span> value;</span><br><span class="line"> <span class="type">pthread_cond_t</span> cond;</span><br><span class="line"> <span class="type">pthread_mutex_t</span> lock; } Zem_t; <span class="comment">// only one thread can call this</span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">Zem_init</span><span class="params">(Zem_t *s, <span class="type">int</span> value)</span> {</span><br><span class="line"> s->value = value;</span><br><span class="line"> Cond_init(&s->cond);</span><br><span class="line"> Mutex_init(&s->lock);</span><br><span class="line">}</span><br><span class="line"><span class="type">void</span> <span class="title function_">Zem_wait</span><span class="params">(Zem_t *s)</span> {</span><br><span class="line"> Mutex_lock(&s->lock); <span class="keyword">while</span> (s->value <= <span class="number">0</span>) Cond_wait(&s->cond, &s->lock);</span><br><span class="line"> s->value--;</span><br><span class="line"> Mutex_unlock(&s->lock);</span><br><span class="line">}</span><br><span class="line"><span class="type">void</span> <span class="title function_">Zem_post</span><span class="params">(Zem_t *s)</span></span><br><span class="line">{ Mutex_lock(&s->lock);</span><br><span class="line"> s->value++;</span><br><span class="line"> Cond_signal(&s->cond);</span><br><span class="line"> Mutex_unlock(&s->lock);</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>课后习题</strong></p>
<h2 id="第-32-章-常见的并发问题"><a href="#第-32-章-常见的并发问题" class="headerlink" title="第 32 章 常见的并发问题"></a>第 32 章 常见的并发问题</h2><h3 id="非死锁缺陷"><a href="#非死锁缺陷" class="headerlink" title="非死锁缺陷"></a>非死锁缺陷</h3><h2 id="第-31-章:信号量-1"><a href="#第-31-章:信号量-1" class="headerlink" title="第 31 章:信号量"></a>第 31 章:信号量</h2><h3 id="信号量定义-1"><a href="#信号量定义-1" class="headerlink" title="信号量定义"></a>信号量定义</h3><p>信号量作为锁和条件变量</p>
<p>是有一个整数值的对象,可以用两个函数来操作它</p>
<p>**sem_wait()**要么立刻返回(调用 sem_wait()时,信号量的值大于等于 1),要么会让调用线程挂起,直到之后的一个 post 操作。当然,也可能多个调用线程都调用 sem_wait(),因此都在队列中等待被唤醒。</p>
<p><strong>sem_post()</strong> sem_post()并没有等待某些条件满足。它直接增加信号量的值,如果有等待线程,唤醒其中一个。当信号量的值为负数时,这个值就是等待线程的个数</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 int sem_wait(sem_t *s) {</span><br><span class="line">2 decrement the value of semaphore s by one</span><br><span class="line">3 wait if value of semaphore s is negative</span><br><span class="line">4 } int sem_post(sem_t *s) {</span><br><span class="line">7 increment the value of semaphore s by one</span><br><span class="line">8 if there are one or more threads waiting, wake one</span><br><span class="line">9 }</span><br></pre></td></tr></table></figure>
<h3 id="二值信号量(锁)-1"><a href="#二值信号量(锁)-1" class="headerlink" title="二值信号量(锁)"></a>二值信号量(锁)</h3><p>用信号量作为锁。信号量初始值为 1</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">sem_t m;</span><br><span class="line">sem_init(&m, 0, X); // initialize semaphore to X; what should X be?</span><br><span class="line"></span><br><span class="line">sem_wait(&m);</span><br><span class="line">// critical section here</span><br><span class="line">sem_post(&m);</span><br></pre></td></tr></table></figure>
<p>考虑两种场景</p>
<p>1.一个单线程,调用了 sem_wait(),它把信号量的值减为 0。然后,它只会在值小于 0 时等待。因为值是 0,调用线程从函数返回并继续,线程 0 现在可以自由进入临界区。线程 0 在临界区中,如果没有其他线程尝试获取锁,当它调用 sem_post()时,会将信号量重置为 1(因为没有等待线程,不会唤醒其他线程)。</p>
<p><img src="https://s2.loli.net/2022/01/09/jbrgBnfmG9HLztK.png" alt="image-20211117102303687"></p>
<h3 id="信号量用作条件变量-1"><a href="#信号量用作条件变量-1" class="headerlink" title="信号量用作条件变量"></a>信号量用作条件变量</h3><p>信号量初始值应为 1.</p>
<p>有两种情况需要考虑</p>
<p>第一种,父线程创建了子线程,但是子线程并没有运行。这种情况下(见表 31.3),父线程调用 sem_wait()会先于子线程调用 sem_post()。我们希望父线程等待子线程运行。为此,唯一的办法是让信号量的值不大于 0。因此,0 为初值。父线程运行,将信号量减为 −1,然后睡眠等待;子线程运行的时候,调用 sem_post(),信号量增加为 0,唤醒父线程,父线程然后从 sem_wait()返回,完成该程序。</p>
<p>第二种情况是子线程在父线程调用 sem_wait()之前就运行结束(见表 31.4)。在这种情况下,<br>子线程会先调用 sem_post(),将信号量从 0 增加到 1。然后当父线程有机会运行时,会调用<br>sem_wait(),发现信号量的值为 1。于是父线程将信号量从 1 减为 0,没有等待,直接从<br>sem_wait()返回,也达到了预期效果。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span> <span class="type">sem_t</span> s;</span><br><span class="line"><span class="number">2</span></span><br><span class="line"><span class="number">3</span> <span class="type">void</span> *<span class="title function_">child</span><span class="params">(<span class="type">void</span> *arg)</span> {</span><br><span class="line"><span class="number">5</span> <span class="built_in">printf</span>(<span class="string">"child\n"</span>);</span><br><span class="line"><span class="number">6</span> sem_post(&s); <span class="comment">// signal here: child is done</span></span><br><span class="line"><span class="number">7</span> <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line"><span class="number">8</span> }</span><br><span class="line"><span class="number">9</span></span><br><span class="line"><span class="number">10</span> <span class="type">int</span></span><br><span class="line"><span class="number">11</span> main(<span class="type">int</span> argc, <span class="type">char</span> *argv[]) {</span><br><span class="line"><span class="number">12</span> sem_init(&s, <span class="number">0</span>, X); <span class="comment">// what should X be?</span></span><br><span class="line"><span class="number">13</span> <span class="built_in">printf</span>(<span class="string">"parent: begin\n"</span>);</span><br><span class="line"><span class="number">14</span> <span class="type">pthread_t</span> c;</span><br><span class="line"><span class="number">15</span> Pthread_create(c, <span class="literal">NULL</span>, child, <span class="literal">NULL</span>);</span><br><span class="line"><span class="number">16</span> sem_wait(&s); <span class="comment">// wait here for child</span></span><br><span class="line"><span class="number">17</span> <span class="built_in">printf</span>(<span class="string">"parent: end\n"</span>);</span><br><span class="line"><span class="number">18</span> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"><span class="number">19</span> }</span><br></pre></td></tr></table></figure>
<h3 id="生产者-x2F-消费者(有界缓冲区)问题"><a href="#生产者-x2F-消费者(有界缓冲区)问题" class="headerlink" title="生产者/消费者(有界缓冲区)问题"></a>生产者/消费者(有界缓冲区)问题</h3><h4 id="初次尝试-1"><a href="#初次尝试-1" class="headerlink" title="初次尝试"></a>初次尝试</h4><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span> <span class="type">sem_t</span> empty; <span class="comment">//生产者的条件判断变量,相当于缓冲区的剩余容量,被初始化为MAX</span></span><br><span class="line"><span class="number">2</span> <span class="type">sem_t</span> full; <span class="comment">//消费者的条件判断变量,相当于缓冲区的数据数量,初始值为0</span></span><br><span class="line"><span class="number">3</span></span><br><span class="line"><span class="number">4</span> <span class="type">void</span> *<span class="title function_">producer</span><span class="params">(<span class="type">void</span> *arg)</span> {</span><br><span class="line"><span class="number">5</span> <span class="type">int</span> i;</span><br><span class="line"><span class="number">6</span> <span class="keyword">for</span> (i = <span class="number">0</span>; i < loops; i++) {</span><br><span class="line"><span class="number">7</span> sem_wait(&empty); <span class="comment">//line P1 //初始为MAX,函数返回,执行放入</span></span><br><span class="line"><span class="number">8</span> put(i); <span class="comment">// line P2</span></span><br><span class="line"><span class="number">9</span> sem_post(&full); <span class="comment">// line P3 // full+1</span></span><br><span class="line"><span class="number">10</span> }</span><br><span class="line"><span class="number">11</span> }</span><br><span class="line"><span class="number">12</span></span><br><span class="line"><span class="number">13</span> <span class="type">void</span> *<span class="title function_">consumer</span><span class="params">(<span class="type">void</span> *arg)</span> {</span><br><span class="line"><span class="number">14</span> <span class="type">int</span> i, tmp = <span class="number">0</span>;</span><br><span class="line"><span class="number">15</span> <span class="keyword">while</span> (tmp != <span class="number">-1</span>) {</span><br><span class="line"><span class="number">16</span> sem_wait(&full); <span class="comment">// line C1 //full初始值为0,缓冲区没有数据,wait函数将full-1,进入等待状态</span></span><br><span class="line"><span class="number">17</span> tmp = get(); <span class="comment">// line C2</span></span><br><span class="line"><span class="number">18</span> sem_post(&empty); <span class="comment">// line C3</span></span><br><span class="line"><span class="number">19</span> <span class="built_in">printf</span>(<span class="string">"%d\n"</span>, tmp);</span><br><span class="line"><span class="number">20</span> }</span><br><span class="line"></span><br><span class="line"><span class="number">21</span> }</span><br><span class="line"><span class="number">22</span></span><br><span class="line"><span class="number">23</span> <span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> {</span><br><span class="line"><span class="number">24</span> <span class="comment">// ...</span></span><br><span class="line"><span class="number">25</span> sem_init(&empty, <span class="number">0</span>, MAX); <span class="comment">// MAX buffers are empty to begin with...</span></span><br><span class="line"><span class="number">26</span> sem_init(&full, <span class="number">0</span>, <span class="number">0</span>); <span class="comment">// ... and 0 are full</span></span><br><span class="line"><span class="number">27</span> <span class="comment">// ...</span></span><br><span class="line"><span class="number">28</span> }</span><br></pre></td></tr></table></figure>
<p><strong>存在的问题:竞态条件</strong></p>
<p>我们现在假设 MAX 大于 1(比如 MAX=10)。对于这个例子,假定有多个生产者,多<br>个消费者。现在就有问题了:竞态条件。假设两个生产者(Pa 和 Pb)几乎同时调用 put()。当 Pa 先运行,在 f1 行先加入第一条数据(fill=0),假设 Pa 在将 fill 计数器更新为 1 之前被中断,Pb 开始运行,也在 f1 行给缓冲区的 0 位置加入一条数据,这意味着那里的老数据被覆盖!</p>
<h4 id="解决竞态-增加互斥-1"><a href="#解决竞态-增加互斥-1" class="headerlink" title="解决竞态:增加互斥"></a>解决竞态:增加互斥</h4><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 sem_t empty;</span><br><span class="line">2 sem_t full;</span><br><span class="line">3 sem_t mutex;</span><br><span class="line">4</span><br><span class="line">5 void *producer(void *arg) {</span><br><span class="line">6 int i;</span><br><span class="line">7 for (i = 0; i < loops; i++) {</span><br><span class="line">8 sem_wait(&mutex); // line p0 (NEW LINE)</span><br><span class="line">9 sem_wait(&empty); // line p1</span><br><span class="line">10 put(i); // line p2</span><br><span class="line">11 sem_post(&full); // line p3</span><br><span class="line">12 sem_post(&mutex); // line p4 (NEW LINE)</span><br><span class="line">13 }</span><br><span class="line">14 }</span><br><span class="line">15</span><br><span class="line">16 void *consumer(void *arg) {</span><br><span class="line">17 int i;</span><br><span class="line">18 for (i = 0; i < loops; i++) {</span><br><span class="line">19 sem_wait(&mutex); // line c0 (NEW LINE)</span><br><span class="line">20 sem_wait(&full); // line c1</span><br><span class="line">21 int tmp = get(); // line c2</span><br><span class="line">22 sem_post(&empty); // line c3</span><br><span class="line">23 sem_post(&mutex); // line c4 (NEW LINE)</span><br><span class="line">24 printf("%d\n", tmp);</span><br><span class="line">25 }</span><br><span class="line">26 }</span><br><span class="line">27</span><br><span class="line">28 int main(int argc, char *argv[]) {</span><br><span class="line">29 // ...</span><br><span class="line">30 sem_init(&empty, 0, MAX); // MAX buffers are empty to begin with...</span><br><span class="line">31 sem_init(&full, 0, 0); // ... and 0 are full</span><br><span class="line">32 sem_init(&mutex, 0, 1); // mutex=1 because it is a lock (NEW LINE)</span><br><span class="line">33 // ...</span><br><span class="line">34 }</span><br></pre></td></tr></table></figure>
<h5 id="存在的问题-死锁-1"><a href="#存在的问题-死锁-1" class="headerlink" title="存在的问题:死锁"></a>存在的问题:死锁</h5><p>假设有两个线程,一个生产者和一个消费者。消费者首先运行,获得锁(c0 行),然后对 full 信号量执行 sem_wait() (c1 行)。因为还没有数据,所以消费者阻塞,让出 CPU。但是,重要的是,此时消费者仍然持有锁。然后生产者运行。假如生产者能够运行,它就能生产数据并唤醒消费者线程。遗憾的是,它首先对二值互斥信号量调用 sem_wait()(p0 行)。锁已经被持有,因此生产者也被卡住。这里出现了一个循环等待。消费者持有互斥量,等待在 full 信号量上。生产者可以发送 full 信号,却在等待互斥量。因此,生产者和消费者互相等待对方——典型的死锁。</p>
<h4 id="解决死锁-减少锁的作用域-1"><a href="#解决死锁-减少锁的作用域-1" class="headerlink" title="解决死锁:减少锁的作用域"></a>解决死锁:减少锁的作用域</h4><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"> <span class="type">void</span> *<span class="title function_">producer</span><span class="params">(<span class="type">void</span> *arg)</span> {</span><br><span class="line"><span class="number">6</span> <span class="type">int</span> i;</span><br><span class="line"><span class="number">7</span> <span class="keyword">for</span> (i = <span class="number">0</span>; i < loops; i++) {</span><br><span class="line"><span class="number">8</span> sem_wait(&empty); <span class="comment">// line p1</span></span><br><span class="line"><span class="number">9</span> sem_wait(&mutex); <span class="comment">// line p1.5 (MOVED MUTEX HERE...)</span></span><br><span class="line"><span class="number">10</span> put(i); <span class="comment">// line p2</span></span><br><span class="line"><span class="number">11</span> sem_post(&mutex); <span class="comment">// line p2.5 (... AND HERE)</span></span><br><span class="line"><span class="number">12</span> sem_post(&full); <span class="comment">// line p3</span></span><br><span class="line"><span class="number">13</span> }</span><br><span class="line"><span class="number">14</span> }</span><br><span class="line"><span class="number">15</span></span><br><span class="line"><span class="number">16</span> <span class="type">void</span> *<span class="title function_">consumer</span><span class="params">(<span class="type">void</span> *arg)</span> {</span><br><span class="line"><span class="number">17</span> <span class="type">int</span> i;</span><br><span class="line"><span class="number">18</span> <span class="keyword">for</span> (i = <span class="number">0</span>; i < loops; i++) {</span><br><span class="line"><span class="number">19</span> sem_wait(&full); <span class="comment">// line c1</span></span><br><span class="line"><span class="number">20</span> sem_wait(&mutex); <span class="comment">// line c1.5 (MOVED MUTEX HERE...)</span></span><br><span class="line"><span class="number">21</span> <span class="type">int</span> tmp = get(); <span class="comment">// line c2</span></span><br><span class="line"><span class="number">22</span> sem_post(&mutex); <span class="comment">// line c2.5 (... AND HERE)</span></span><br><span class="line"><span class="number">23</span> sem_post(&empty); <span class="comment">// line c3</span></span><br><span class="line"><span class="number">24</span> <span class="built_in">printf</span>(<span class="string">"%d\n"</span>, tmp);</span><br><span class="line"><span class="number">25</span> }</span><br><span class="line"><span class="number">26</span> }</span><br></pre></td></tr></table></figure>
<h3 id="读者—写者锁-1"><a href="#读者—写者锁-1" class="headerlink" title="读者—写者锁"></a>读者—写者锁</h3><p>不同的数据结构访问可能需要不同类型的锁。</p>
<p>如果某个线程要更新数据结构,需要调用 rwlock_acquire_lock()获得写锁,调用 rwlock_release_writelock()释放锁。内部通过一个 writelock 的信号量保证只有一个写者能获得锁进入临界区,从而更新数据结构。</p>
<p>但有一些缺陷,尤其是公平性。读者很容易饿死写者。存在复杂一些的解决方案,也许你可以想到更好的实现?提示:有写者等待时,如何能够避更多的读者进入并持有锁。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="number">1</span> <span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">rwlock_t</span> {</span></span><br><span class="line"><span class="number">2</span> <span class="type">sem_t</span> lock; <span class="comment">// binary semaphore (basic lock)</span></span><br><span class="line"><span class="number">3</span> <span class="type">sem_t</span> writelock; <span class="comment">// used to allow ONE writer or MANY readers</span></span><br><span class="line"><span class="number">4</span> <span class="type">int</span> readers; <span class="comment">// count of readers reading in critical section</span></span><br><span class="line"><span class="number">5</span> } <span class="type">rwlock_t</span>;</span><br><span class="line"><span class="number">6</span></span><br><span class="line"><span class="number">7</span> <span class="type">void</span> <span class="title function_">rwlock_init</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">8</span> rw->readers = <span class="number">0</span>;</span><br><span class="line"><span class="number">9</span> sem_init(&rw->lock, <span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line"><span class="number">10</span> sem_init(&rw->writelock, <span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line"><span class="number">11</span> }</span><br><span class="line"><span class="number">12</span></span><br><span class="line"><span class="number">13</span> <span class="type">void</span> <span class="title function_">rwlock_acquire_readlock</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">14</span> sem_wait(&rw->lock);</span><br><span class="line"><span class="number">15</span> rw->readers++;</span><br><span class="line"><span class="number">16</span> <span class="keyword">if</span> (rw->readers == <span class="number">1</span>)</span><br><span class="line"><span class="number">17</span> sem_wait(&rw->writelock); <span class="comment">// first reader acquires writelock</span></span><br><span class="line"><span class="number">18</span> sem_post(&rw->lock);</span><br><span class="line"><span class="number">19</span> }</span><br><span class="line"><span class="number">20</span></span><br><span class="line"><span class="number">21</span> <span class="type">void</span> <span class="title function_">rwlock_release_readlock</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">22</span> sem_wait(&rw->lock);</span><br><span class="line"><span class="number">23</span> rw->readers--;</span><br><span class="line"><span class="number">24</span> <span class="keyword">if</span> (rw->readers == <span class="number">0</span>)</span><br><span class="line"><span class="number">25</span> sem_post(&rw->writelock); <span class="comment">// last reader releases writelock</span></span><br><span class="line"><span class="number">26</span> sem_post(&rw->lock);</span><br><span class="line"><span class="number">27</span> }</span><br><span class="line"><span class="number">28</span></span><br><span class="line"><span class="number">29</span> <span class="type">void</span> <span class="title function_">rwlock_acquire_writelock</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">30</span> sem_wait(&rw->writelock);</span><br><span class="line"><span class="number">31</span> }</span><br><span class="line"><span class="number">32</span></span><br><span class="line"><span class="number">33</span> <span class="type">void</span> <span class="title function_">rwlock_release_writelock</span><span class="params">(<span class="type">rwlock_t</span> *rw)</span> {</span><br><span class="line"><span class="number">34</span> sem_post(&rw->writelock);</span><br><span class="line"><span class="number">35</span> }</span><br></pre></td></tr></table></figure>
<h3 id="哲学家就餐问题-1"><a href="#哲学家就餐问题-1" class="headerlink" title="哲学家就餐问题"></a>哲学家就餐问题</h3><p>假定有 5 位“哲学家”围着一个圆桌。每两位哲学家之间有一把餐叉(一共 5 把)。哲学家有时要思考一会,不需要餐叉;有时又要就餐。而一位哲学家只有同时拿到了左手边和右手边的两把餐叉,才能吃到东西。关于餐叉的竞争以及随之而来的同步问题,就是我们在并发编程中研<br>究它的原因。</p>
<p><img src="https://s2.loli.net/2022/01/09/VWqDckdmHh279oz.png" alt="image-20211117112437466"></p>
<h4 id="有问题的解决方案-1"><a href="#有问题的解决方案-1" class="headerlink" title="有问题的解决方案"></a>有问题的解决方案</h4><p>如果哲学家 p 希望用左手边的叉子,他们就调用 left(p)。类似地,右手边的叉子就用<br>right(p)。模运算解决了最后一个哲学家(p = 4)右手边叉子的编号问题,就是餐叉 0。</p>
<p>为了拿到餐叉,我们依次获取每把餐叉的锁——先是左手边的,然后是右手边的。结束就餐时,释放掉锁</p>
<p>**问题:死锁 ** 假设每个哲学家都拿到了左手边的餐叉,他们每个都会阻塞住,并且一直等待另一个餐叉。具体来说,哲学家 0 拿到了餐叉 0,哲学家 1 拿到了餐叉 1,哲学家 2 拿到餐叉 2,哲学家 3 拿到餐叉 3,哲学家 4 拿到餐叉 4。所有的餐叉都被占有了,所有的哲学家都阻塞着,并且等待另一个哲学家占有的餐叉</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 void getforks() {</span><br><span class="line">2 sem_wait(forks[left(p)]);</span><br><span class="line">3 sem_wait(forks[right(p)]);</span><br><span class="line">4 }</span><br><span class="line">5</span><br><span class="line">6 void putforks() {</span><br><span class="line">7 sem_post(forks[left(p)]);</span><br><span class="line">8 sem_post(forks[right(p)]);</span><br><span class="line">9 }</span><br></pre></td></tr></table></figure>
<h4 id="解决方案-破除依赖-1"><a href="#解决方案-破除依赖-1" class="headerlink" title="解决方案:破除依赖"></a>解决方案:破除依赖</h4><p>改变某位哲学家的用餐顺序,因为最后一个哲学家会尝试先拿右手边的餐叉,然后拿左手边,所以不会出现每个哲学家都拿着一个餐叉,卡住等待另一个的情况,等待循环被打破了</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">1 void getforks() {</span><br><span class="line">2 if (p == 4) {</span><br><span class="line">3 sem_wait(forks[right(p)]);</span><br><span class="line">4 sem_wait(forks[left(p)]);</span><br><span class="line">5 } else {</span><br><span class="line">6 sem_wait(forks[left(p)]);</span><br><span class="line">7 sem_wait(forks[right(p)]);</span><br><span class="line">8 }</span><br><span class="line">9 }</span><br></pre></td></tr></table></figure>
<h3 id="如何实现信号量-1"><a href="#如何实现信号量-1" class="headerlink" title="如何实现信号量"></a>如何实现信号量</h3><p>我们用底层的同步原语(锁和条件变量),来实现自己的信号量,名字叫作 Zemaphore。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">Zem_t</span> {</span></span><br><span class="line"> <span class="type">int</span> value;</span><br><span class="line"> <span class="type">pthread_cond_t</span> cond;</span><br><span class="line"> <span class="type">pthread_mutex_t</span> lock;</span><br><span class="line"> } Zem_t;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// only one thread can call this</span></span><br><span class="line"> <span class="type">void</span> <span class="title function_">Zem_init</span><span class="params">(Zem_t *s, <span class="type">int</span> value)</span> {</span><br><span class="line"> s->value = value;</span><br><span class="line"> Cond_init(&s->cond);</span><br><span class="line"> Mutex_init(&s->lock);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">void</span> <span class="title function_">Zem_wait</span><span class="params">(Zem_t *s)</span> {</span><br><span class="line"> Mutex_lock(&s->lock);</span><br><span class="line"> <span class="keyword">while</span> (s->value <= <span class="number">0</span>)</span><br><span class="line"> Cond_wait(&s->cond, &s->lock);</span><br><span class="line"> s->value--;</span><br><span class="line"> Mutex_unlock(&s->lock);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">void</span> <span class="title function_">Zem_post</span><span class="params">(Zem_t *s)</span> {</span><br><span class="line"></span><br><span class="line"> Mutex_lock(&s->lock);</span><br><span class="line"> s->value++;</span><br><span class="line"> Cond_signal(&s->cond);</span><br><span class="line"> Mutex_unlock(&s->lock);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id=""><a href="#" class="headerlink" title=""></a></h3>]]></content>
<categories>
<category>The-Operating-System-Notes</category>
</categories>
<tags>
<tag>operating sysytem</tag>
<tag>linux</tag>
</tags>
</entry>
<entry>
<title>3.operating-systems-three-easy-pieces answers</title>
<url>/2021/05/11/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%AC%AC4%E6%AC%A1%E4%BD%9C%E4%B8%9A/</url>
<content><![CDATA[<h1 id="操作系统第-4-次作业"><a href="#操作系统第-4-次作业" class="headerlink" title="操作系统第 4 次作业"></a>操作系统第 4 次作业</h1><p>201908010224 黄雅妮</p>
<h2 id="第-37-章——磁盘驱动器"><a href="#第-37-章——磁盘驱动器" class="headerlink" title="第 37 章——磁盘驱动器"></a>第 37 章——磁盘驱动器</h2><h3 id="37-1"><a href="#37-1" class="headerlink" title="37.1"></a>37.1</h3><p><strong>计算以下几组请求的寻道、旋转和传输时间:-a 0,-a 6,-a 30,-a 7,30,8,最后 -a 10,11,12,13。</strong></p>
<p>运行以下命令行</p>
<figure class="highlight shell"><figcaption><span>script</span></figcaption><table><tr><td class="code"><pre><span class="line">python2 disk.py -a 0 -G</span><br><span class="line">python2 disk.py -a 6 -G</span><br><span class="line">python2 disk.py -a 30 -G</span><br><span class="line">python2 disk.py -a 7,30,8 -G</span><br><span class="line">python2 disk.py -a 10,11,12,13 -G</span><br></pre></td></tr></table></figure>
<p><strong>对于 -a 0,</strong></p>
<p>Rotate = 165</p>
<p>Transfer = 30</p>
<p>seek = 0</p>
<p>total = 165 + 30 + 0 =195</p>
<p><strong>对于 -a 6,</strong></p>
<p>Rotate = 345</p>
<p>Transfer = 30</p>
<p>seek = 0</p>
<p>total = 345 + 30 + 0 =375</p>
<p><strong>对于 -a 30,</strong></p>
<p>Rotate = 265</p>
<p>Transfer = 30</p>
<p>seek = 80</p>
<p>total =375</p>
<p><strong>对于 -a 7,30,8,</strong></p>
<p>Rotate = 545</p>
<p>Transfer = 90</p>
<p>seek = 160</p>
<p>total = 795</p>
<p><strong>对于 -a 10,11,12,13</strong></p>
<p>Rotate = 425</p>
<p>Transfer =120</p>
<p>seek =40</p>
<p>total = 585</p>
<h3 id="37-2"><a href="#37-2" class="headerlink" title="37.2"></a>37.2</h3><p><strong>执行上述相同请求,但将寻道速率更改为不同值:-S 2,-S 4,-S 8,-S 10,-S 40,-S 0.1。时间如何变化?</strong></p>
<table>
<thead>
<tr>
<th></th>
<th>-a 0</th>
<th>-a 30</th>
<th>-a 7,30,8</th>
<th>-a 10,11,12,13</th>
</tr>
</thead>
<tbody><tr>
<td>- S 2</td>
<td>195</td>
<td>375</td>
<td>795</td>
<td>585</td>
</tr>
<tr>
<td>-S 4</td>
<td>195</td>
<td>375</td>
<td>435</td>
<td>585</td>
</tr>
<tr>
<td>-S 8</td>
<td>195</td>
<td>375</td>
<td>435</td>
<td>585</td>
</tr>
<tr>
<td>-S 10</td>
<td>195</td>
<td>375</td>
<td>435</td>
<td>585</td>
</tr>
<tr>
<td>-S 40</td>
<td>195</td>
<td>375</td>
<td>435</td>
<td>585</td>
</tr>
<tr>
<td>-S 0.1</td>
<td>195</td>
<td>1095</td>
<td>2235</td>
<td>945</td>
</tr>
</tbody></table>
<h3 id="37-3"><a href="#37-3" class="headerlink" title="37.3"></a>37.3</h3><table>
<thead>
<tr>
<th></th>
<th>-a 0</th>
<th>-a 30</th>
<th>-a 7,30,8</th>
<th>-a 10,11,12,13</th>
</tr>
</thead>
<tbody><tr>
<td>- R 0.1</td>
<td>1950</td>
<td>3750</td>
<td>4349</td>
<td>5850</td>
</tr>
<tr>
<td>- R 0.5</td>
<td>390</td>
<td>750</td>
<td>1590</td>
<td>1170</td>
</tr>
<tr>
<td>- R 0.01</td>
<td>19500</td>
<td>37501</td>
<td>43500</td>
<td>58501</td>
</tr>
</tbody></table>
<h3 id="37-4"><a href="#37-4" class="headerlink" title="37.4"></a>37.4</h3><p>你可能已经注意到,对于一些请求流,一些策略比 FIFO 更好。例如,对于请求流-a 7,30,8,处理请求的顺序是什么?现在在相同的工作负载上运行最短寻道时间优先(SSTF)调度程序(-p SSTF)。每个请求服务需要多长时间(寻道、旋转、传输)?</p>
<p>FIFO:7,30,8</p>
<p>SSTF:7,8,30</p>
<table>
<thead>
<tr>
<th></th>
<th>seek</th>
<th>rotate</th>
<th>transfer</th>
</tr>
</thead>
<tbody><tr>
<td>FIFO</td>
<td>160</td>
<td>545</td>
<td>90</td>
</tr>
<tr>
<td>SSTF</td>
<td>80</td>
<td>205</td>
<td>90</td>
</tr>
</tbody></table>
<h3 id="37-5"><a href="#37-5" class="headerlink" title="37.5"></a><strong>37.5</strong></h3><p><strong>现在做同样的事情,但使用最短的访问时间优先(SATF)调度程序(-p SATF)。它是否对-a 7,30,8 指定的一组请求有所不同?找到 SATF 明显优于 SSTF 的一组请求。出现显著差异的条件是什么?</strong></p>
<p>使用 SATF,磁盘的访问顺序依然是 7 , 8, 30。寻道,旋转,传输都是一样的</p>
<p>-a 12,31 -c -S 40 -R 3 -p SSTF</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Block: 12 Seek: 1 Rotate: 54 Transfer: 10 Total: 65</span><br><span class="line">Block: 31 Seek: 1 Rotate: 59 Transfer: 10 Total: 70</span><br><span class="line"></span><br><span class="line">TOTALS Seek: 2 Rotate:113 Transfer: 20 Total: 135</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>-a 12,31 -c -S 40 -R 3 -p SATF</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Block: 31 Seek: 2 Rotate: 3 Transfer: 10 Total: 15</span><br><span class="line">Block: 12 Seek: 1 Rotate: 39 Transfer: 10 Total: 50</span><br><span class="line"></span><br><span class="line">TOTALS Seek: 3 Rotate: 42 Transfer: 20 Total: 65</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>运行以上两组参数。显然 SATF 优于 SSTF.</p>
<p>只要是,寻道时间远远小于旋转时间的情况,SATF 性能就会显著优于 SSTF。</p>
<h3 id="37-6"><a href="#37-6" class="headerlink" title="37.6"></a>37.6</h3><p><strong>你可能已经注意到,该磁盘没有特别好地处理请求流-a 10,11,12,13。这是为什么?你可以引入一个磁道偏斜来解决这个问题(-o skew,其中 skew 是一个非负整数)?考虑到默认寻道速率,偏斜应该是多少,才能尽量减少这一组请求的总时间?对于不同的寻道速率(例如,-S 2,-S 4)呢?一般来说,考虑到寻道速率和扇区布局信息,你能否写出一个公式来计算偏斜?</strong></p>
<p>寻道时间太长,导致更换磁道时,刚好旋转超过了 12,导致需要重新旋转一个周期</p>
<p><img src="https://s2.loli.net/2022/01/03/p3BAPZMqlvUgNVH.png" alt="image-20220103194714898"></p>
<p><img src="https://s2.loli.net/2022/01/03/eVHXtKjTdF2GcIm.png" alt="image-20220103194623054"></p>
<p>跨越相邻磁道的寻道时间约接近(小于)旋转过一个扇区的旋转时间,效果越好。</p>
<p>如果寻道速度为 V,寻道距离为 D,旋转角速度为 W,每扇区区域跨越的角度为 A,偏斜为 O,<strong>那么:<code>D / V < A*O / W</code> ,所以偏斜量<code>O > DW / VA</code>,并取满足该条件下的最小的整数。</strong></p>
<h2 id="第-38-章——RAID"><a href="#第-38-章——RAID" class="headerlink" title="第 38 章——RAID"></a>第 38 章——RAID</h2><h3 id="38-1"><a href="#38-1" class="headerlink" title="38.1"></a>38.1</h3><p><strong>使用模拟器执行一些基本的 RAID 映射测试。运行不同的级别(0、1、4、5),看看你是否可以找出一组请求的映射。对于 RAID-5,看看你是否可以找出左对称(left-symmetric)和左不对称(left-asymmetric)布局之间的区别。使用一些不同的随机种子,产生不同于上面的问题。</strong></p>
<p>运行以下命令行</p>
<p>❯ python2 raid.py -n 10 -L 5 -5 LS -c -W seq</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">LOGICAL READ from addr:0 size:4096</span><br><span class="line"> read [disk 0, offset 0]</span><br><span class="line">LOGICAL READ from addr:1 size:4096</span><br><span class="line"> read [disk 1, offset 0]</span><br><span class="line">LOGICAL READ from addr:2 size:4096</span><br><span class="line"> read [disk 2, offset 0]</span><br><span class="line">LOGICAL READ from addr:3 size:4096</span><br><span class="line"> read [disk 3, offset 1]</span><br><span class="line">LOGICAL READ from addr:4 size:4096</span><br><span class="line"> read [disk 0, offset 1]</span><br><span class="line">LOGICAL READ from addr:5 size:4096</span><br><span class="line"> read [disk 1, offset 1]</span><br><span class="line">LOGICAL READ from addr:6 size:4096</span><br><span class="line"> read [disk 2, offset 2]</span><br><span class="line">LOGICAL READ from addr:7 size:4096</span><br><span class="line"> read [disk 3, offset 2]</span><br><span class="line">LOGICAL READ from addr:8 size:4096</span><br><span class="line"> read [disk 0, offset 2]</span><br><span class="line">LOGICAL READ from addr:9 size:4096</span><br><span class="line"> read [disk 1, offset 3]</span><br></pre></td></tr></table></figure>
<p>❯ python2 raid.py -n 10 -L 5 -5 LA -c -W seq</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">LOGICAL READ from addr:0 size:4096</span><br><span class="line"> read [disk 0, offset 0]</span><br><span class="line">LOGICAL READ from addr:1 size:4096</span><br><span class="line"> read [disk 1, offset 0]</span><br><span class="line">LOGICAL READ from addr:2 size:4096</span><br><span class="line"> read [disk 2, offset 0]</span><br><span class="line">LOGICAL READ from addr:3 size:4096</span><br><span class="line"> read [disk 0, offset 1]</span><br><span class="line">LOGICAL READ from addr:4 size:4096</span><br><span class="line"> read [disk 1, offset 1]</span><br><span class="line">LOGICAL READ from addr:5 size:4096</span><br><span class="line"> read [disk 3, offset 1]</span><br><span class="line">LOGICAL READ from addr:6 size:4096</span><br><span class="line"> read [disk 0, offset 2]</span><br><span class="line">LOGICAL READ from addr:7 size:4096</span><br><span class="line"> read [disk 2, offset 2]</span><br><span class="line">LOGICAL READ from addr:8 size:4096</span><br><span class="line"> read [disk 3, offset 2]</span><br><span class="line">LOGICAL READ from addr:9 size:4096</span><br><span class="line"> read [disk 1, offset 3]</span><br><span class="line"></pre></span><br></pre></td></tr></table></figure>
<p>从上面读取的磁盘与偏移可以推测 left- symmetric)和 left-asymmetric 的物理磁盘布局:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">left-symmetric left-asymmetric</span><br><span class="line">0 1 2 P 0 1 2 P</span><br><span class="line">4 5 P 3 3 4 P 5</span><br><span class="line">8 P 6 7 6 P 7 8</span><br></pre></td></tr></table></figure>
<h3 id="38-2"><a href="#38-2" class="headerlink" title="38.2"></a>38.2</h3><p><strong>与第一个问题一样,但这次使用 -C 来改变块的大小。大块的大小如何改变映射?</strong></p>
<p>❯ python2 raid.py -n 20 -L 5 -5 LS -c -W seq -C 8K</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">LOGICAL READ from addr:0 size:4096</span><br><span class="line"> read [disk 0, offset 0]</span><br><span class="line">LOGICAL READ from addr:1 size:4096</span><br><span class="line"> read [disk 0, offset 1]</span><br><span class="line">LOGICAL READ from addr:2 size:4096</span><br><span class="line"> read [disk 1, offset 0]</span><br><span class="line">LOGICAL READ from addr:3 size:4096</span><br><span class="line"> read [disk 1, offset 1]</span><br><span class="line">LOGICAL READ from addr:4 size:4096</span><br><span class="line"> read [disk 2, offset 0]</span><br><span class="line">LOGICAL READ from addr:5 size:4096</span><br><span class="line"> read [disk 2, offset 1]</span><br><span class="line">LOGICAL READ from addr:6 size:4096</span><br><span class="line"> read [disk 3, offset 2]</span><br><span class="line">LOGICAL READ from addr:7 size:4096</span><br><span class="line"> read [disk 3, offset 3]</span><br><span class="line">LOGICAL READ from addr:8 size:4096</span><br><span class="line"> read [disk 0, offset 2]</span><br><span class="line">LOGICAL READ from addr:9 size:4096</span><br><span class="line"> read [disk 0, offset 3]</span><br></pre></td></tr></table></figure>
<p>布局推测:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">0 2 4 P</span><br><span class="line">1 3 5 P</span><br><span class="line">8 10 P 6</span><br><span class="line">9 11 P 7</span><br></pre></td></tr></table></figure>
<h3 id="38-3"><a href="#38-3" class="headerlink" title="38.3"></a><strong>38.3</strong></h3><p><strong>执行上述测试,但使用 r 标志来反转每个问题的性质。</strong></p>
<p>可以看到,磁盘布局没有发生变化</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">❯ python2 raid.py -n 12 -L 5 -5 LS -c -W seq -C 8K -r</span><br><span class="line">LOGICAL READ from addr:0 size:4096</span><br><span class="line"> read [disk 0, offset 0]</span><br><span class="line">LOGICAL READ from addr:1 size:4096</span><br><span class="line"> read [disk 0, offset 1]</span><br><span class="line">LOGICAL READ from addr:2 size:4096</span><br><span class="line"> read [disk 1, offset 0]</span><br><span class="line">LOGICAL READ from addr:3 size:4096</span><br><span class="line"> read [disk 1, offset 1]</span><br><span class="line">LOGICAL READ from addr:4 size:4096</span><br><span class="line"> read [disk 2, offset 0]</span><br><span class="line">LOGICAL READ from addr:5 size:4096</span><br><span class="line"> read [disk 2, offset 1]</span><br><span class="line">LOGICAL READ from addr:6 size:4096</span><br><span class="line"> read [disk 3, offset 2]</span><br><span class="line">LOGICAL READ from addr:7 size:4096</span><br><span class="line"> read [disk 3, offset 3]</span><br><span class="line">LOGICAL READ from addr:8 size:4096</span><br><span class="line"> read [disk 0, offset 2]</span><br><span class="line">LOGICAL READ from addr:9 size:4096</span><br><span class="line"> read [disk 0, offset 3]</span><br><span class="line">LOGICAL READ from addr:10 size:4096</span><br><span class="line"> read [disk 1, offset 2]</span><br><span class="line">LOGICAL READ from addr:11 size:4096</span><br><span class="line"> read [disk 1, offset 3]</span><br></pre></td></tr></table></figure>
<h3 id="38-4"><a href="#38-4" class="headerlink" title="38.4"></a>38.4</h3><p><strong>现在使用反转标志,但用-S 标志增加每个请求的大小。尝试指定 8 KB、12 KB 和 16 KB 的大小,同时改变 RAID 级别。当请求的大小增加时,底层 IO 模式会发生什么?请务必在顺序工作负载上尝试此操作(-W sequential)。对于什么请求大小,RAID-4 和 RAID-5 的 I0 效率更高?</strong></p>
<p>1.当请求块大小超过磁盘块大小时,一个请求需要读写多个磁盘</p>
<p>2.对于 RAID-4 和 RAID-5,请求块大小为 16 K 时,效率更高,因为可以同时利用多个磁盘,相当于全条带写入</p>
<h3 id="38-5"><a href="#38-5" class="headerlink" title="38.5"></a>38.5</h3><p><strong>使用模拟器的定时模式(-t)来估计 100 次随机读取到 RAID 的性能,同时改变 RAID 级别,使用 4 个磁盘。</strong></p>
<p>运行以下命令行,可以看到 RAID0 的性能是最优的,RAID4 由于可使用磁盘为 4-1=3,读取性能较差</p>
<p><code>python2 raid.py -L 0 -t -n 100 -c -D 4</code></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">disk:0 busy: 100.00 I/Os: 28 (sequential:0 nearly:1 random:27)</span><br><span class="line">disk:1 busy: 93.91 I/Os: 29 (sequential:0 nearly:6 random:23)</span><br><span class="line">disk:2 busy: 87.92 I/Os: 24 (sequential:0 nearly:0 random:24)</span><br><span class="line">disk:3 busy: 65.94 I/Os: 19 (sequential:0 nearly:1 random:18)</span><br><span class="line"></span><br><span class="line">STAT totalTime 275.7</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p><code>python2 raid.py -L 1 -t -n 100 -c -D 4</code></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">disk:0 busy: 100.00 I/Os: 28 (sequential:0 nearly:1 random:27)</span><br><span class="line">disk:1 busy: 86.98 I/Os: 24 (sequential:0 nearly:0 random:24)</span><br><span class="line">disk:2 busy: 97.52 I/Os: 29 (sequential:0 nearly:3 random:26)</span><br><span class="line">disk:3 busy: 65.23 I/Os: 19 (sequential:0 nearly:1 random:18)</span><br><span class="line"></span><br><span class="line">STAT totalTime 278.7</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p><code>python2 raid.py -L 4 -t -n 100 -c -D 4</code></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">disk:0 busy: 78.48 I/Os: 30 (sequential:0 nearly:0 random:30)</span><br><span class="line">disk:1 busy: 100.00 I/Os: 40 (sequential:0 nearly:3 random:37)</span><br><span class="line">disk:2 busy: 76.46 I/Os: 30 (sequential:0 nearly:2 random:28)</span><br><span class="line">disk:3 busy: 0.00 I/Os: 0 (sequential:0 nearly:0 random:0)</span><br><span class="line"></span><br><span class="line">STAT totalTime 386.1</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p><code>python2 raid.py -L 5 -t -n 100 -c -D 4</code></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">disk:0 busy: 100.00 I/Os: 28 (sequential:0 nearly:1 random:27)</span><br><span class="line">disk:1 busy: 95.84 I/Os: 29 (sequential:0 nearly:5 random:24)</span><br><span class="line">disk:2 busy: 87.60 I/Os: 24 (sequential:0 nearly:0 random:24)</span><br><span class="line">disk:3 busy: 65.70 I/Os: 19 (sequential:0 nearly:1 random:18)</span><br><span class="line"></span><br><span class="line">STAT totalTime 276.7</span><br></pre></td></tr></table></figure>
<h2 id="第-40-章——文件系统的实现"><a href="#第-40-章——文件系统的实现" class="headerlink" title="第 40 章——文件系统的实现"></a>第 40 章——文件系统的实现</h2><h3 id="40-1"><a href="#40-1" class="headerlink" title="40.1"></a>40.1</h3><p><strong>用一些不同的随机种子(比如 17、18、19、20)运行模拟器,看看你是否能确定每次状态变化之间一定发生了哪些操作。</strong></p>
<p>以随机种子 -s 17 为例</p>
<p>运行以下命令行</p>
<p><code>python2 vsfs.py -n 6 -s 17</code></p>
<p>每步发生的操作写在注释中</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">Initial state</span><br><span class="line"></span><br><span class="line">inode bitmap 10000000</span><br><span class="line">inodes [d a:0 r:2] [] [] [] [] [] [] []</span><br><span class="line">data bitmap 10000000</span><br><span class="line">data [(.,0) (..,0)] [] [] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">Which operation took place?</span><br><span class="line">#创建了目录‘/u’ mkdir('/u')</span><br><span class="line">inode bitmap 11000000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [] [] [] [] [] []</span><br><span class="line">data bitmap 11000000</span><br><span class="line">data [(.,0) (..,0) (u,1)] [(.,1) (..,0)] [] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">Which operation took place?</span><br><span class="line">#创建文件‘/a’ create('/a')</span><br><span class="line">inode bitmap 11100000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [f a:-1 r:1] [] [] [] [] []</span><br><span class="line">data bitmap 11000000</span><br><span class="line">data [(.,0) (..,0) (u,1) (a,2)] [(.,1) (..,0)] [] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">Which operation took place?</span><br><span class="line">#删除文件‘/a’ unlink('/a')</span><br><span class="line">inode bitmap 11000000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [] [] [] [] [] []</span><br><span class="line">data bitmap 11000000</span><br><span class="line">data [(.,0) (..,0) (u,1)] [(.,1) (..,0)] [] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">Which operation took place?</span><br><span class="line">#创建目录‘/z’ mkdir(/z)</span><br><span class="line">inode bitmap 11100000</span><br><span class="line">inodes [d a:0 r:4] [d a:1 r:2] [d a:2 r:2] [] [] [] [] []</span><br><span class="line">data bitmap 11100000</span><br><span class="line">data [(.,0) (..,0) (u,1) (z,2)] [(.,1) (..,0)] [(.,2) (..,0)] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">Which operation took place?</span><br><span class="line">#创建目录‘/s’ mkdir('/s')</span><br><span class="line">inode bitmap 11110000</span><br><span class="line">inodes [d a:0 r:5] [d a:1 r:2] [d a:2 r:2] [d a:3 r:2] [] [] [] []</span><br><span class="line">data bitmap 11110000</span><br><span class="line">data [(.,0) (..,0) (u,1) (z,2) (s,3)] [(.,1) (..,0)] [(.,2) (..,0)] [(.,3) (..,0)] [] [] [] []</span><br><span class="line"></span><br><span class="line">Which operation took place?</span><br><span class="line">#创建文件‘/z/x’ create(/z/x)</span><br><span class="line">inode bitmap 11111000</span><br><span class="line">inodes [d a:0 r:5] [d a:1 r:2] [d a:2 r:2] [d a:3 r:2] [f a:-1 r:1] [] [] []</span><br><span class="line">data bitmap 11110000</span><br><span class="line">data [(.,0) (..,0) (u,1) (z,2) (s,3)] [(.,1) (..,0)] [(.,2) (..,0) (x,4)] [(.,3) (..,0)] [] [] [] []</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h3 id="40-2"><a href="#40-2" class="headerlink" title="40.2"></a>40.2</h3><p><strong>现在使用不同的随机种子(比如 21、22、23、24),但使用-r 标志运行,这样做可以让你在显示操作时猜测状态的变化。关于 inode 和数据块分配算法,根据它们喜欢分配的块,你可以得出什么结论?</strong></p>
<p>可以看到,操作系统按照最近可分配原则分配 inode 和数据块</p>
<p><code>python2 vsfs.py -s 21 -r</code></p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Initial state</span><br><span class="line"></span><br><span class="line">inode bitmap 10000000</span><br><span class="line">inodes [d a:0 r:2] [] [] [] [] [] [] []</span><br><span class="line">data bitmap 10000000</span><br><span class="line">data [(.,0) (..,0)] [] [] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">mkdir("/o");</span><br><span class="line"></span><br><span class="line">inode bitmap 11000000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [] [] [] [] [] []</span><br><span class="line">data bitmap 11000000</span><br><span class="line">data [(.,0) (..,0) (o,1)] [(.,1) (..,0)] [] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">creat("/b");</span><br><span class="line"></span><br><span class="line">inode bitmap 11100000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [f a:-1 r:1] [] [] [] [] []</span><br><span class="line">data bitmap 11000000</span><br><span class="line">data [(.,0) (..,0) (o,1) (b,2)] [(.,1) (..,0)] [] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">creat("/o/q");</span><br><span class="line"></span><br><span class="line">inode bitmap 11110000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [f a:-1 r:1] [f a:-1 r:1] [] [] [] []</span><br><span class="line">data bitmap 11000000</span><br><span class="line">data [(.,0) (..,0) (o,1) (b,2)] [(.,1) (..,0) (q,3)] [] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">fd=open("/b", O_WRONLY|O_APPEND); write(fd, buf, BLOCKSIZE); close(fd);</span><br><span class="line"></span><br><span class="line">inode bitmap 11110000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [f a:2 r:1] [f a:-1 r:1] [] [] [] []</span><br><span class="line">data bitmap 11100000</span><br><span class="line">data [(.,0) (..,0) (o,1) (b,2)] [(.,1) (..,0) (q,3)] [m] [] [] [] [] []</span><br><span class="line"></span><br><span class="line">fd=open("/o/q", O_WRONLY|O_APPEND); write(fd, buf, BLOCKSIZE); close(fd);</span><br><span class="line"></span><br><span class="line">inode bitmap 11110000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [f a:2 r:1] [f a:3 r:1] [] [] [] []</span><br><span class="line">data bitmap 11110000</span><br><span class="line">data [(.,0) (..,0) (o,1) (b,2)] [(.,1) (..,0) (q,3)] [m] [j] [] [] [] []</span><br><span class="line"></span><br><span class="line">creat("/o/j");</span><br><span class="line"></span><br><span class="line">inode bitmap 11111000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [f a:2 r:1] [f a:3 r:1] [f a:-1 r:1] [] [] []</span><br><span class="line">data bitmap 11110000</span><br><span class="line">data [(.,0) (..,0) (o,1) (b,2)] [(.,1) (..,0) (q,3) (j,4)] [m] [j] [] [] [] []</span><br><span class="line"></span><br><span class="line">unlink("/b");</span><br><span class="line"></span><br><span class="line">inode bitmap 11011000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [] [f a:3 r:1] [f a:-1 r:1] [] [] []</span><br><span class="line">data bitmap 11010000</span><br><span class="line">data [(.,0) (..,0) (o,1)] [(.,1) (..,0) (q,3) (j,4)] [] [j] [] [] [] []</span><br><span class="line"></span><br><span class="line">fd=open("/o/j", O_WRONLY|O_APPEND); write(fd, buf, BLOCKSIZE); close(fd);</span><br><span class="line"></span><br><span class="line">inode bitmap 11011000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [] [f a:3 r:1] [f a:2 r:1] [] [] []</span><br><span class="line">data bitmap 11110000</span><br><span class="line">data [(.,0) (..,0) (o,1)] [(.,1) (..,0) (q,3) (j,4)] [g] [j] [] [] [] []</span><br><span class="line"></span><br><span class="line">creat("/o/x");</span><br><span class="line"></span><br><span class="line">inode bitmap 11111000</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:2] [f a:-1 r:1] [f a:3 r:1] [f a:2 r:1] [] [] []</span><br><span class="line">data bitmap 11110000</span><br><span class="line">data [(.,0) (..,0) (o,1)] [(.,1) (..,0) (q,3) (j,4) (x,2)] [g] [j] [] [] [] []</span><br><span class="line"></span><br><span class="line">mkdir("/o/t");</span><br><span class="line"></span><br><span class="line">inode bitmap 11111100</span><br><span class="line">inodes [d a:0 r:3] [d a:1 r:3] [f a:-1 r:1] [f a:3 r:1] [f a:2 r:1] [d a:4 r:2] [] []</span><br><span class="line">data bitmap 11111000</span><br><span class="line">data [(.,0) (..,0) (o,1)] [(.,1) (..,0) (q,3) (j,4) (x,2) (t,5)] [g] [j] [(.,5) (..,1)] [] [] []</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<h3 id="40-3"><a href="#40-3" class="headerlink" title="40.3"></a>40.3</h3><p><strong>现在将文件系统中的数据块数量减少到非常少(比如两个),并用 100 个左右的请求来运行模拟器。在这种高度约束的布局中,哪些类型的文件最终会出现在文件系统中?什么类型的操作会失败?</strong></p>
<p>只有 inode 和 data 会出现在文件系统中</p>
<p>mkdir 操作会失败, create 操作不会失败,但 create 操作不会添加数据块</p>
<h3 id="40-4"><a href="#40-4" class="headerlink" title="40.4"></a>40.4</h3><p><strong>现在做同样的事情,但针对 inodes。只有非常少的 inode,什么类型的操作才能成功?哪些通常会失败?文件系统的最终状态可能是什么?</strong></p>
<p>mkdir 和 create 操作都会失败,最终文件系统的状态如下(vsfs 最后一个数据块不能使用)</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">inode bitmap 10</span><br><span class="line">inodes [d a:0 r:2] []</span><br><span class="line">data bitmap 10000000</span><br><span class="line">data [(.,0) (..,0)] [] [] [] [] [] [] []</span><br></pre></td></tr></table></figure>
]]></content>
<categories>
<category>The-Operating-System-Notes</category>
</categories>
<tags>
<tag>operating sysytem</tag>
<tag>linux</tag>
</tags>
</entry>
<entry>
<title>并发性</title>
<url>/2021/05/15/%E6%8C%81%E4%B9%85%E6%80%A7/</url>
<content><![CDATA[<h1 id="持久性"><a href="#持久性" class="headerlink" title="持久性"></a>持久性</h1><h2 id="第-36-章-I-x2F-O-设备"><a href="#第-36-章-I-x2F-O-设备" class="headerlink" title="第 36 章 I/O 设备"></a>第 36 章 I/O 设备</h2><h3 id="系统架构"><a href="#系统架构" class="headerlink" title="系统架构"></a>系统架构</h3><p>先看一个典型系统的架构:</p>
<p><img src="https://s2.loli.net/2022/01/04/6VZGklTiugHsnxY.png" alt="image-20220104191656489"></p>
<p>CPU 通过内存总线连接到系统内存,图像或者其它高性能 I/O 设备通过常规的 I/O 总线连接到系统,外围总线(SCSI,SATA,USB)将最慢的设备连接到系统中</p>
<p>采用这样的布局,是因为越短的总线越快,因此高性能的内存总线没有足够的空间连接太多设备,且高性能总线的造价很高,所以采用这种分层的布局,让要求高性能的设备(显卡)离 CPU 更近一点,低性能的设备离 CPU 远一点,将磁盘和其它低速设备连接到外围总线的好处有很多,如你可以在外围总线上连接大量的设备</p>
<h3 id="标准设备"><a href="#标准设备" class="headerlink" title="标准设备"></a>标准设备</h3><p>这是一个标准设备,通过它可以理解设备交互的机制,这个标准设备包含两部分重要组件</p>
<p><strong>硬件接口</strong>:同软件一样,硬件也需要一些接口,让系统来控制它的操作,所有的设备都有自己的特定接口以及特有的交互协议</p>
<p><strong>内部结构</strong>:包含设备功能的实现,一些非常简单的设备通常用一个或几个芯片来实现它们的功能,更复杂的设备会包含简单的 CPU,一些通用内存,设备相关的特定芯片,来完成它们的工作,如现代 RAID 控制器通常包含上千行固件(硬件中的软件)<br><img src="https://s2.loli.net/2022/01/04/Rs5MnIegQawJZuK.png" alt="image-20220104191807544"></p>
<h3 id="标准协议"><a href="#标准协议" class="headerlink" title="标准协议"></a>标准协议</h3><p>在标准设备的示意图中,设备接口包含了 3 个寄存器,一个状态寄存器(用于读取并查看当前设备的状态),一个命令寄存器(用于通知设备执行某项任务),一个数据寄存器(将数据传给设备或者从设备接收数据),通过读写标准设备的这些寄存器,操作系统就可以控制该设备的行为</p>
<p>一个简单的交互协议:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">//轮询设备当前状态</span><br><span class="line">while(STATUS == BUSY) {</span><br><span class="line"> ;</span><br><span class="line">}</span><br><span class="line">//向数据寄存器和命令寄存器写入数据</span><br><span class="line">Write data to DATA register</span><br><span class="line">Write Command to COMMAND register</span><br><span class="line">//轮询设备是否成功执行命令</span><br><span class="line">while(STATUS == BUSY) {</span><br><span class="line"> ;</span><br></pre></td></tr></table></figure>
<p>这个简单的标准协议包含 4 步:<br>1,操作系统反复读取状态寄存器,等待设备进入可以直接接收命令的就绪状态,称为轮询设备<br>2,操作系统下发数据到数据寄存器<br>3,操作系统将命令写入命令寄存器,这时设备就知道数据已经准备好了,它开始执行命令<br>4,操作系统不断轮询设备,等待并判断设备是否完成了命令(可能得到一个代表执行成功或失败的数据)</p>
<p>这个协议简单且有效,但难免有些低效和不方便,第一个问题就是轮询比较低效,在等待设备执行完成命令时浪费了大量 CPU 时间,如果此时操作系统切换到下一个就绪进程,就可以大大提高 CPU 的利用率</p>
<h3 id="用中断减少-CPU-开销"><a href="#用中断减少-CPU-开销" class="headerlink" title="用中断减少 CPU 开销"></a>用中断减少 CPU 开销</h3><p>利用中断可以减少 CPU 的开销,在上述的标准协议中,有了中断,CPU 可以不用通过轮询设备来判断设备是否成功执行命令,而是向设备发出一个请求,然后让当前发起 I/O 的进程睡眠,切换执行其它进程,当设备执行完命令后,会抛出一个硬件中断,引发 CPU 跳转执行系统预先定义好的中断服务例程或中断处理程序,它会唤醒先前发起 I/O 的进程继续执行</p>
<p>因此中断允许计算与 I/O 重叠,这是提高 CPU 利用率的关键</p>
<p>但是使用中断也<strong>并非是最佳</strong>方案,考虑下面两个场景:<br>1,如果有一个非常高性能的设备,它处理请求很快,通常在 CPU 第一次轮询就能返回结果,如果此时使用中断,反而会让系统变慢,使用中断切换到其它进程,处理中断再切换回来带来了进程切换的开销,如果设备很快,那么最好的方法反而是轮询,如果设备较慢,那么采用允许发生重叠的中断更好,如果设备速度时慢时快,那么可以采用<strong>混合策略,先轮询一小段时间,设备还没有完成命令时,再使用中断</strong><br>2,在网络中,网络端收到大量数据包,如果每个包引发一次中断,那么可能导致操作系统不断处理中断而无法处理用户的请求,<strong>这种情况下,采用轮询可以更好控制系统的行为,让服务器先处理一些请求,再轮询网卡是否有数据包到达</strong></p>
<p>对于中断的处理也可以优化,通过合并,设备在抛出中断前先等待一小段时间,在此期间其它请求可能也会完成,就可以将多个中断合并成一次中断抛出,从而降低处理中断的代价</p>
<h3 id="利用-DMA-进行更高效的数据传送"><a href="#利用-DMA-进行更高效的数据传送" class="headerlink" title="利用 DMA 进行更高效的数据传送"></a>利用 DMA 进行更高效的数据传送</h3><p>DMA 引擎是操作系统中的一个特殊设备,它可以协调完成内存和设备间的数据传递,不需要 CPU 介入</p>
<p>DMA 的工作过程:为了将数据传送给设备,操作系统通过编程告诉 DMA 引擎需要的数据所在内存的位置,要拷贝的大小以及要拷贝到哪个设备,之后操作系统就可以处理其他请求了,当 DMA 的任务完成后,DMA 控制器会抛出一个中断告诉操作系统自己已经完成了数据传输</p>
<p>数据的拷贝都是由 DMA 完成的,因此 CPU 在此时是空闲的,所以操作系统可以让它做一些其它事情,如调度其它进程法</p>