-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
994 lines (414 loc) · 88.8 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
<!DOCTYPE html>
<html class="theme-next mist use-motion" lang="zh-Hans">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">
<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />
<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />
<link href="/css/main.css?v=5.1.3" rel="stylesheet" type="text/css" />
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.3">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.3">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.3">
<link rel="mask-icon" href="/images/logo.svg?v=5.1.3" color="#222">
<meta name="keywords" content="Hexo, NexT" />
<meta name="description" content="Da Shen's blog">
<meta property="og:type" content="website">
<meta property="og:title" content="dashen's blog">
<meta property="og:url" content="http://www.dashen.im/index.html">
<meta property="og:site_name" content="dashen's blog">
<meta property="og:description" content="Da Shen's blog">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="dashen's blog">
<meta name="twitter:description" content="Da Shen's blog">
<script type="text/javascript" id="hexo.configurations">
var NexT = window.NexT || {};
var CONFIG = {
root: '/',
scheme: 'Mist',
version: '5.1.3',
sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
fancybox: true,
tabs: true,
motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
duoshuo: {
userId: '0',
author: '博主'
},
algolia: {
applicationID: '',
apiKey: '',
indexName: '',
hits: {"per_page":10},
labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
}
};
</script>
<link rel="canonical" href="http://www.dashen.im/"/>
<title>dashen's blog</title>
</head>
<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">
<div class="container sidebar-position-left
page-home">
<div class="headband"></div>
<header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-wrapper">
<div class="site-meta ">
<div class="custom-logo-site-title">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<span class="site-title">dashen's blog</span>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<p class="site-subtitle"></p>
</div>
<div class="site-nav-toggle">
<button>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
<span class="btn-bar"></span>
</button>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section">
<i class="menu-item-icon fa fa-fw fa-home"></i> <br />
首页
</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section">
<i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
归档
</a>
</li>
</ul>
</nav>
</div>
</header>
<main id="main" class="main">
<div class="main-inner">
<div class="content-wrap">
<div id="content" class="content">
<section id="posts" class="posts-expand">
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://www.dashen.im/2017/12/12/异常控制流/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Da Shen">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="dashen's blog">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2017/12/12/异常控制流/" itemprop="url">异常控制流</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2017-12-12T15:31:08+08:00">
2017-12-12
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>处理器处理指令的机制可以十分简单的看做一条接着一条的执行,这一条条的指令构成的序列,称为<strong>逻辑控制流</strong>。在我们的程序中,有两种方式可以改变这种控制流:<strong>条件分支和函数调用</strong>,但是这两种方式仅仅是作用于程序本身的控制,无法面对更加复杂的情况,例如:网卡中有数据到达、定时器的计时完成或者是在键盘上按下了ctrl-c等。那么为了应对这些复杂的情况,计算机中就提出了<strong>异常控制流</strong>的机制。</p>
<h1 id="异常"><a href="#异常" class="headerlink" title="异常"></a>异常</h1><p>异常是异常控制流的一中形式,它一部分有由件实现,一部分由操作系统实现。在这里我们讨论的异常和我们在Java中所讨论的异常并不是一种内容,这里的异常更多是为了让<strong>处理器响应某种变化的机制</strong>。通过下图来简要的描述一下异常:<br><img src="/2017/12/12/异常控制流/exception.png" alt="image"><br>CPU正在执行应用程序的指令,在执行到<code>当前指令</code>的时候,异常发生了比如网卡中有数据到达。这个时候CPU就会通过查找一张叫做<strong>异常表</strong>的跳转表,查看该异常应该调用哪个异常处理程序。接着异常处理程序处理完成之后,就会根据处理结果做出响应,有三种不同的响应:</p>
<ul>
<li>重新执行异常发生时正在执行的指令,也就是<em>当前指令</em>。</li>
<li>应用程序直接执行后续指令,也就是<em>下一指令</em>。</li>
<li>应用程序终止退出。</li>
</ul>
<p>从这几种返回结果便可以看出,异常处理机制和函数调用机制十分不同,函数调用机制的返回就像是异常机制的一个子集。一般来说异常可以分为四类:中断(interrupt)、陷阱(trap)、故障(fault)、终止(abort),下表是分别对这些类别的总结:</p>
<table>
<thead>
<tr>
<th>类别</th>
<th>原因</th>
<th>异步/同步</th>
<th>返回行为</th>
</tr>
</thead>
<tbody>
<tr>
<td>中断</td>
<td>来自I/O设备的信号</td>
<td>异步</td>
<td>总是返回到下一条指令</td>
</tr>
<tr>
<td>陷阱</td>
<td>有意的异常</td>
<td>同步</td>
<td>总是返回到下一条指令</td>
</tr>
<tr>
<td>故障</td>
<td>潜在可以恢复的作物</td>
<td>同步</td>
<td>总是返回到当前指令</td>
</tr>
<tr>
<td>终止</td>
<td>不可恢复的错误</td>
<td>步同</td>
<td>不会返回</td>
</tr>
</tbody>
</table>
<h2 id="中断"><a href="#中断" class="headerlink" title="中断"></a>中断</h2><p>中断是在所有异常中唯一的异步发生的,它是由处理器外部设备发出的,而不是由任何一条硬件指令造成的。例如网卡中有数据到达、磁盘找到目标数据、定时器计时时间到等都会向CPU发出一个中断信号造成中断,使得CPU去处理异常。<br><img src="/2017/12/12/异常控制流/interrupt.png" alt="image"><br>让CPU感知到异常到来的方式都是通过其他外围硬件设备引起CPU的某一引脚的电压增高,从而让CPU在执行完当前指令之后,控制转换去中断处理行为。</p>
<h2 id="陷阱"><a href="#陷阱" class="headerlink" title="陷阱"></a>陷阱</h2><p>陷阱是一种同步的异常,它更像是一种有意的行为。陷阱最主要的用途便是让进程在<strong>用户态</strong>和<strong>内核态</strong>之间切换,这一切换过程便是系统调用,例如,创建进程(fork)、读文件(read)等。如过从我们这些程序员的角度去看陷阱异常的返回机制,它其实就普通的函数调用一样,最后都会返回到下一条指令处。举一个具体的例子:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">Ltmp2:</div><div class="line"> movq %rsp, %rbp</div><div class="line"> callq _fork</div><div class="line"> xorl %eax, %eax</div><div class="line"> popq %rbp</div><div class="line"> retq</div></pre></td></tr></table></figure></p>
<p>在这里,假设用户调用了<code>fork</code>函数用于创建进程,系统实际上会执行<code>_fork</code>函数,使得当前进程进入内核态,最后通过<code>syscall</code>调用相应的汇编指令创建出新的子进程。</p>
<h2 id="故障"><a href="#故障" class="headerlink" title="故障"></a>故障</h2><p>故障异常相对来说比较特殊,因为它要根据故障处理程序是否能成功的解决故障而决定返回的情况。如果故障能够被解决那么就会再执行一遍引起故障的指令,如果无法解决那么应用程序就会终止退出。<br><img src="/2017/12/12/异常控制流/fault.png" alt="image"><br>一个十分经典的故障便是<strong>缺页故障</strong>,当指令引用一个虚拟地址时,而相应地址的数据并不在内存中的时候就会引起缺页故障。这是故障处理程序就会从磁盘中去取出相应的数据,接在再重新执行指令,这个时候引用的数据已经存在于内存中了,就不会再引起故障了。</p>
<h2 id="终止"><a href="#终止" class="headerlink" title="终止"></a>终止</h2><p>最后一种是终止异常,它一般都是有不可恢复的致命错误导致的结果,通常来说是硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。碰到终止异常后,终止处理程序处理之后不会将控制权返换给应用程序而是将控制权交给一个<code>abort</code>程序,这个程序最终会停止应用程序。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>此篇内容主要是对四中异常的介绍,在此基础上会引申出非常多的知识点,这些知识点我们可以通过在学习Linux系统的调用的时候一一学习到。了解异常可以帮助我们在日后掌握理解并发、应用程序和操作系统交互甚至是在编写有意思的应用程序时提供帮助。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://www.dashen.im/2017/11/16/链接/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Da Shen">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="dashen's blog">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2017/11/16/链接/" itemprop="url">链接</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2017-11-16T16:00:25+08:00">
2017-11-16
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>源代码需要经过编译和链接两大步骤才能转换成计算机可以执行的机器码。因此了解链接的过程也有助于我们构建自己的知识体系,可以明白什么是链接,什么是动态库和静态库,以及什么是可重定位的目标文件等等。</p>
<h1 id="链接器"><a href="#链接器" class="headerlink" title="链接器"></a>链接器</h1><p>链接的核心目的就是将各个各种代码和数据片段合并起来成为一个文件的过程。例如我们最简单的<code>Hello World</code>程序<br><figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><stdio.h></span></span></div><div class="line"></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span>{</div><div class="line"> <span class="built_in">printf</span>(<span class="string">"%s\n"</span>, <span class="string">"hello world"</span>);</div><div class="line"> <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>它调用了<code>printf()</code>函数,但是在经过汇编器处理之后生成的<code>hello.o</code>文件中并没有包含<code>printf</code>函数相关的代码,而仅仅是一个<strong>符号引用</strong>罢了,那要让<code>hello world</code>程序最后能够执行就需要经过链接,把<code>printf</code>的定义和引用相关联,组合成一个文件。最终这一个文件才能被加载到内存当中用于后续的执行。链接在早期的计算机中还是需要手动执行的,但是到了现代系统中,链接都是交由链接器去完成的。链接器从宏观看起来就像是这样:<br><img src="/2017/11/16/链接/link.png" alt="image"><br>我们的源代码在经过预处理、编译、汇编之后会转换成<strong>可重定位的目标文件</strong>,链接器会把这些文件经过符号解析和重定位过程组合在一起生成一个<strong>可执行的目标文件</strong>,这个文件就可以直接加载到内存中运行了。正是因为有了链接器的存在,便可以让我们进行<strong>分离编译</strong>开发了,把哪些公共的部分抽象出来,单独放在一个模块中,需要用的时候再通过链接器把他们串联在一起。在需要修改的时候也只需要修改模块本身然后编译即可,而不用编译其他的文件,这便是链接器的目的——分离编译。</p>
<h2 id="链接的时机"><a href="#链接的时机" class="headerlink" title="链接的时机"></a>链接的时机</h2><p>链接的过程可以是在<strong>编译的</strong>时候,可以是在程序<strong>加载</strong>到内存的时候,甚至可以在程序<strong>运行</strong>的时候。因此我们便将在编译时期的链接过程称为静态链接,而在加载和运行时期的链接则被称为动态链接。与之相对应的便是静态链接器和动态链接器,静态链接器的过程就像是上图一样,而动态链接会涉及到加载器的部分,我们会在后续介绍。</p>
<h1 id="目标文件"><a href="#目标文件" class="headerlink" title="目标文件"></a>目标文件</h1><p>在我们链接过程的前后都涉及到了目标文件,因此我们有必要先去了解一下目标文件知识。目标文件可以分为三类:</p>
<ul>
<li>可重定位的目标文件。包含二进制的代码和数据,可以与其他可重定位目标文件组合起来形成一个可执行的目标文件。</li>
<li>可执行的目标文件。包含二进制的代码和数据,可以直接被加载到内存并执行。</li>
<li>共享目标文件。一种特殊的可重定位的目标文件,可以在加载和运行期间被动态地加载到内存。</li>
</ul>
<p>目标文件最终都是以一种格式存放在磁盘当中的,在Linux和Unix的操作系统中是采用<strong>可执行可链接格式(ELF, Executable and Linkable Format)</strong>存放目标文件的。下图就是展示了<strong>可重定位目标文件</strong>的ELF格式:<br><img src="/2017/11/16/链接/relocate-elf.png" alt="image"><br>在图中每一小格都被称为节,每个节都有它自己的用途。例如.text节就是放了编译好的机器码,.rodata存放了只读数据,以及.symtab保存的是符号表。在这里我们主要来关注一下符号表这一节,这一块的内容涉及到了符号解析的过程,因此有必要着重看一下。</p>
<h2 id="符号"><a href="#符号" class="headerlink" title="符号"></a>符号</h2><p>符号其实就是我们代码里的各种变量和函数的名字,在链接器的视角来说,一共有三种类型的符号:</p>
<ul>
<li>全局符号。由模块自身定义并可以被其他模块引用的符号,对应于本源码文件中的非静态的C函数和全局变量。</li>
<li>外部符号。由其他模块定义并被模块自身引用的符号,对应于其他源码文件中定义的非静态C函数和全局变量。</li>
<li>局部符号。本模块定义和引用的符号,对应于带static属性的C函数和静态变量。</li>
</ul>
<blockquote>
<p>static关键字在C中的含义其实和Java中的private关键字类似,写了static关键字的全局变量或者函数,支对模块(文件)可见,其他模块是不能引用这些函数和全局变量的。</p>
</blockquote>
<h2 id="符号表"><a href="#符号表" class="headerlink" title="符号表"></a>符号表</h2><p>符号表就是描述符号的集合,符号表就是一个包含<strong>条目</strong>的数组,条目就是一个用来<strong>描述符号</strong>的数据结构,条目的格式如下:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> {</div><div class="line"> <span class="keyword">int</span> name; <span class="comment">// 字符表的偏移量</span></div><div class="line"> <span class="keyword">char</span> type:<span class="number">4</span>; <span class="comment">// 函数符号或数据符号</span></div><div class="line"> <span class="keyword">char</span> binding:<span class="number">4</span>; <span class="comment">// 局部或全局</span></div><div class="line"> <span class="keyword">char</span> reserved; </div><div class="line"> <span class="keyword">short</span> section; <span class="comment">// 符号所在的节</span></div><div class="line"> <span class="keyword">long</span> value; <span class="comment">// 符号地址</span></div><div class="line"> <span class="keyword">long</span> size; <span class="comment">// 目标的大小</span></div><div class="line">}Elf64_Symbol;</div></pre></td></tr></table></figure></p>
<p>可以看到这个数据就是用来描述符号的,比如其中<code>name</code>字段就是表明了这个符号叫什么名字,<code>type</code>指出该符号变量的符号还是函数的符号。<code>section</code>字段是用来描述这个符号是在哪一节的,例如一个函数的符号的<code>section</code>就会指向<code>.text</code>这一节,除此之外<code>section</code>还会有三个特殊的值:<code>ABS</code>代表不该被重定位的符号;<code>UNDEF</code>代表未定义的符号,也就是在本目标文件中引用,但是却在其他目标文件中定义的符号;<code>COMMON</code>代表还未被分配位置的未初始化的数据。简单来说要理解符号表,记住一条就是符号表是一个描述目标文件中符号的数组即可。</p>
<h1 id="静态链接"><a href="#静态链接" class="headerlink" title="静态链接"></a>静态链接</h1><p>静态链接就是在编译期间将各个目标组合在一起的过过程,这个过程可以分为两个步骤:符号解析和重定位。</p>
<h2 id="符号解析"><a href="#符号解析" class="headerlink" title="符号解析"></a>符号解析</h2><p>符号解析的目的就是将每个符号的引用和它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。简而言之就是理清楚符号定义与符号引用的关系,将他们对应起来。局部符号是最容易解析的,因为它们的定义和引用都在同一个目标文件内。但是如果是全局符号或者是外部符号这个时候,就会涉及到一个同名问题,如过个模块定义了同名的符号,这个时候链接器就需要去决定使用哪一个了。在Linux中,符号会被分成<strong>强符号</strong>和<strong>弱符号</strong>,强符号是函数和已初始化的全局变量,未初始化的全局变量就是弱符号。在链接的过程中会遵循以下的规则:</p>
<ol>
<li>不允许有多个同名的强符号。</li>
<li>如果有一个强符号和多个弱符号同名,则选择强符号。</li>
<li>如果有多个弱符号同名,则随机选择一个。</li>
</ol>
<p>例如,我们编译链接下面两个C模块<br><figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// foo1.c</span></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// foo2.c</span></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>{</div><div class="line"> <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>可想而知这是无法通过编译链接的,就会输出这样的错误:<br><img src="/2017/11/16/链接/link-error.png" alt="image"><br>因为有两个名字都为main的强符号,链接无法执行而终止。</p>
<h2 id="静态库"><a href="#静态库" class="headerlink" title="静态库"></a>静态库</h2><p>链接的过程试讲各个目标文件组合在一起,但是有时候最终程序只用到目标文件中的一部分的代码,可却把所有目标文件都组合在了一起,这就导致了磁盘占用空间的增大亦或是内存的浪费。因此静态链接通常会涉及到静态库,静态库就是一系列可重定位目标文件的集合,在Linux中会以<code>.a</code>结尾,这些文件都是静态链接库。静态库的好处就是:程序员不需要显式的指定所有需要链接的目标模块,因为指定是一个耗时且容易出错的过程;链接时,连接程序只从静态库中拷贝被程序引用的目标模块,这样就减小了可执行文件在磁盘和内存中的大小。静态链接的过程主要有两部分:符号解析和重定位。例如我们现在有这么两个C模块,<br><figure class="highlight c"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// multvec.c</span></div><div class="line"><span class="keyword">int</span> multcnt = <span class="number">0</span>;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">multvec</span><span class="params">(<span class="keyword">int</span> *x, <span class="keyword">int</span> *y, <span class="keyword">int</span> *z, <span class="keyword">int</span> n)</span> </span>{</div><div class="line"> <span class="keyword">int</span> i;</div><div class="line"> multcnt++;</div><div class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; i < n; i++) {</div><div class="line"> z[i] = x[i] * y[i];</div><div class="line"> }</div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">// addvec.c</span></div><div class="line"><span class="keyword">int</span> addcnt = <span class="number">0</span>;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">addvec</span><span class="params">(<span class="keyword">int</span> *x, <span class="keyword">int</span> *y, <span class="keyword">int</span> *z, <span class="keyword">int</span> n)</span> </span>{</div><div class="line"> <span class="keyword">int</span> i;</div><div class="line"> addcnt++;</div><div class="line"> <span class="keyword">for</span> (i = <span class="number">0</span>; i < n; i++) {</div><div class="line"> z[i] = x[i] + y[i];</div><div class="line"> }</div><div class="line">}</div></pre></td></tr></table></figure></p>
<p>我们可以先编译得到相应的可重定位的目标文件,然后通过<code>ar</code>命令将他们打包成一个静态库。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">linux> gcc -c addvec.c multvec.c # 编译</div><div class="line">linux> ar rcs libvector.a addvec.o multvec.o # 打包</div></pre></td></tr></table></figure></p>
<p>最后我们就会得到打包好的可用于静态链接的静态库。</p>
<h2 id="重定位"><a href="#重定位" class="headerlink" title="重定位"></a>重定位</h2><p>当我们的可重定位的目标文件执行了符号解析,符号定义与引用直接的关系明确之后,就需要执行可重定位流程了,简而言之就下图这样:<br><img src="/2017/11/16/链接/relocate.png" alt="image"><br>把所有输入的可重定位目标文件组合成一个可执行的目标文件。在这个过程中,链接器会先</p>
<ol>
<li>把各个可重定位目标文件相同的节合并,并给它们一个运行时的内存地址,包括符号定义的地方也会分配一个内存地址;</li>
<li>然后符号定义的地址赋给它们引用的地方。<br>通过这两步最终形成一个可执行的目标文件。我们仔细观察一下这个可执行目标文件的ELF格式,发现会有一个<code>.init</code>节,这一部分定义了一个叫做<code>_init</code>函数,在程序初始化的时候回调用它。</li>
</ol>
<h1 id="加载器"><a href="#加载器" class="headerlink" title="加载器"></a>加载器</h1><p>最后这个可执行的目标文件,就会通过加载器(loader)加载到内存当中,在这里我仅仅需要了解程序在内存中的模型即可。<br><img src="/2017/11/16/链接/memory.png" alt="image"><br>从图中可以看到可执行目标文件中的黄色和蓝色部分会被加载到内存部分,作为代码段和数据段。以及在内存还会出现用户栈和运行时可分配的内存空间——堆,这些部分组成了一个程序运行的上下文。</p>
<h1 id="动态链接"><a href="#动态链接" class="headerlink" title="动态链接"></a>动态链接</h1><p>最后来说一下动态链接,在静态链接过程中还是会把很多公用的部分复制到可执行的目标文件中,例如<code>printf</code>,<code>scanf</code>这一类的。动态链接就是为了解决这一问题而实现。动态链接会使用共享库(shared library),共享库就是一个目标模块,在运行或者加载的时候,可以加载到内存的任意位置,并和需要这个共享库的程序链接起来。具体程序在调用的时候就会调用这一块的代码,因此这些共享的部分在内存中只占有一部分空间,而不是像之前分布在不同的内存空间中,造成资源的浪费。共享库在Linux都是<code>.so</code>结尾的,动态链接库只提供符号表和其他少量信息用于保证所有符号引用都有定义,保证编译顺利通过。动态链接器(ld-linux.so)链接程序在运行过程中根据记录的共享对象的符号定义来动态加载共享库,然后完成重定位。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>以上便是链接的知识点,此篇内容还是作为笔记向的内容,帮助自己构架知识体系。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
<article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
<div class="post-block">
<link itemprop="mainEntityOfPage" href="http://www.dashen.im/2017/11/10/计算机中的存储设备/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="name" content="Da Shen">
<meta itemprop="description" content="">
<meta itemprop="image" content="/images/avatar.gif">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="dashen's blog">
</span>
<header class="post-header">
<h1 class="post-title" itemprop="name headline">
<a class="post-title-link" href="/2017/11/10/计算机中的存储设备/" itemprop="url">计算机中的存储设备</a></h1>
<div class="post-meta">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-calendar-o"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建于" itemprop="dateCreated datePublished" datetime="2017-11-10T20:22:44+08:00">
2017-11-10
</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>计算机的执行需要指令和数据,指令和数据必然也需要存储他们的地方,此篇内容就是来讲一下计算机中各种存储设备,看看他们的优缺点,再看看这些设备又是如何跟CPU交互的,最后我们会介绍缓存的相关知识。</p>
<h1 id="存储体系金字塔"><a href="#存储体系金字塔" class="headerlink" title="存储体系金字塔"></a>存储体系金字塔</h1><p>与计算相关的存储技术有很多,他们之间相互配合,取长补短用于计算机中的各个部分,就像下面的这个金字塔一样。<br><img src="/2017/11/10/计算机中的存储设备/pyramid.png" alt="image"><br>在金字塔越高层的地方,越接近CPU,这里的存储设备的速度越快,但是相应的成本也越高,空间也不会很大。同样的在金字塔往下的地方的存储设备的存取速度就会慢很多,成本便宜,也因此可以有很大的存储空间。这种存储层次体系合理的地方就在于:</p>
<ul>
<li>高层存储可以看做是低层存储的缓存。</li>
<li>数据和指令使用是倾向于一种局部性,即不会同时使用全部的指令和数据。</li>
</ul>
<p>基于这两点,这种存储体系就能很好将各种存储技术相结合,最终让CPU高效的处理运算,而减少由于存储存取设备速度慢导致CPU需要等待而出现的CPU资源浪费。此外,这个金字塔中的存储技术其实主要可以分为两类:随机存取存储和传统机械磁盘存储。</p>
<h2 id="随机存取存储器"><a href="#随机存取存储器" class="headerlink" title="随机存取存储器"></a>随机存取存储器</h2><p>随机存储器(RAM)又可以分为两种类型:SRAM和DRAM,<strong>SRAM的访问速度快,但是价格昂贵,一般会用于CPU中的高速缓存</strong>,<strong>DRAM的速度稍慢,但是价格便宜,通常都会用于内存</strong>。从我们的金字塔中也可看到,SRAM被用于作为CPU中的三级缓存,而DRAM就被拿来作为内存了。但是,RAM也有一个缺点就是断电之后,数据是会丢失不被保存的。相应的就会有一种断电不丢数据存储,非易失存储器(ROM,Read-Only Memory),虽然它乍眼一看是Read-Only,只读存储,只读不写,但是其实说起ROM基本都认为是一种可以读写的可持久化存储技术。例如,我们的手机、数码相机中的存储以及固态硬盘(SSD, Solid State Disk)都是基于闪存技术,它也是属于ROM范畴,而且还可以被读写。</p>
<h2 id="传统机械磁盘"><a href="#传统机械磁盘" class="headerlink" title="传统机械磁盘"></a>传统机械磁盘</h2><p>现在SSD虽然慢慢在抢占传统机械磁盘的位置,但是作为价格更加低廉,存储空间更大的机械磁盘也就还是有一席之地。磁盘是由多个盘片组成的,每个盘片的正反两面都覆盖这磁性材料,中间有一个可以旋转的主轴,一般会以5400~15000转每分钟的转速带动盘片旋转。盘片表面十分类似于树的年轮,每一圈年轮叫做磁道,磁盘又被划分为一组组的扇区,每个扇区都能包含相等数量数据(一般512字节),就如下图所示:<br><img src="/2017/11/10/计算机中的存储设备/platter.png" alt="image"><br>最后将这一片片的磁盘和一个带有读写头的传动臂封装在一个密封的盒子内,就构成了一个磁盘驱动器,也就是俗称的硬盘。为了屏蔽这种机械结构的复杂性,现在磁盘还提供了一种<strong>逻辑块</strong>的概念,我们就可以通过逻辑块来访问具体数据了,逻辑块与数据的实际物理位置的映射就交给磁盘控制器去完成。可想而知这种纯粹的机械结构的存取速度根本不能和这些半导体存储相比的,硬盘和DRAM相比较慢了将近2500倍,和SRAM相比则甚至到了4万倍。但是好在我们可以有很多办法减少这种慢速设备带来的影响,传统的机械磁盘由于它的成本等特性,一段时间内也不可能被完全的替代。</p>
<h1 id="总线"><a href="#总线" class="headerlink" title="总线"></a>总线</h1><p>接着我们就来看看CPU是访问内存和磁盘中的指令和数据的。访问存储设备计算机会用到一种叫做总线的并行导线,总线就像是计算机中的高速公路,负责数据,指令,地址的传输通信。CPU与内存,磁盘等设备的交互必须要通过总线,才能将彼此连接起来,就像下图一样:<br><img src="/2017/11/10/计算机中的存储设备/bus.png" alt="image"><br>从图中我们也可以看到,总线有系统总线、内存总线、IO总线这几类。系统总线连接CPU和IO桥,内存总线连接IO桥与内存,IO总线是负责连接各种IO设备的,例如USB设备、显卡、磁盘驱动(硬盘)甚至是网卡这类的设备。然后我们深入了解一下CPU和内存的存取和磁盘的访问过程。</p>
<h2 id="CPU和内存的存取"><a href="#CPU和内存的存取" class="headerlink" title="CPU和内存的存取"></a>CPU和内存的存取</h2><p>每次CPU和内存来来回回的存取过程是通过总线的读事务和写事务完成的,就如字面意思一样,读事务将数据从内存中传送到CPU中,写事务是将数据传送到内存当中。下图就描述了读事务和写事务的过程:<br><img src="/2017/11/10/计算机中的存储设备/cpu-memory-bus.png" alt="image"></p>
<ul>
<li><p>读事务。CPU将地址通过系统总线发送给IO桥,IO桥再通过内存总线转发给内存,最后内存取出相应地址的数据并放入内存总线,通过IO桥,系统总线交给CPU。</p>
</li>
<li><p>写事务。同样的CPU也显示将地址发送到内存总线,这个时候内存从内存总线读出地址后,会等待相应的数据到来。之后CPU将数据再发送给内存,内存最后把数据放入相应地址的空间上。</p>
</li>
</ul>
<h2 id="CPU和磁盘的访问"><a href="#CPU和磁盘的访问" class="headerlink" title="CPU和磁盘的访问"></a>CPU和磁盘的访问</h2><p>CPU访问磁盘等外围设备的数据都是通过IO总线完成的,在这里我们会大致描述一下当CPU从磁盘中读取数据的时候发生了什么。首先内存会用<strong>内存映射</strong>技术,简单说这个技术就是在虚拟内存地址空间内分配一块与磁盘相对应的地址空间,访问磁盘数据就可以像是访问内存一样。我们可以通过下图来了解这一过程:<br><img src="/2017/11/10/计算机中的存储设备/cpu-disk-bus.png" alt="image"><br>首先CPU会将命令、逻辑块号、以及磁盘数据需要存放在内存什么地方的目的地址通过总线发送到硬盘上,也就是磁盘控制器上。磁盘控制器会解析这些信号并执行找到相应的数据,并通过DMA技术(直接将数据发送到内存,无需CPU等待并参与)将数据发送到目的内存地址的空间上。等到DMA执行完毕,即数据发送完毕之后,磁盘驱动器会发出一个CPU中断(图中虚线部分)通知CPU数据已经到达内存了,你可以通过访问内存来获得磁盘数据了,最后CPU就会执行内存访问获得具体的数据。这个便是CPU与磁盘的访问过程。</p>
<h1 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h1><p>最后我们就来看缓存的部分,目的是知道计算机中的缓存是怎么一回事情。缓存的核心思想就是高层高速设备作为对低层慢速设备的缓冲,比如CPU中的高速缓存就是内存的缓冲,内存就是硬盘的缓冲,硬盘就是远程文件的缓冲等。低层设备是以<strong>块(block)的形式</strong>存储的,每一个快都有一个唯一的地址,类似的高层缓冲设备也是块的形式存储设备,但是却没有像低层设备这么多。因此说来,任何时刻缓存都只是保存低层存储数据副本的子集。我们以下图为例:<br><img src="/2017/11/10/计算机中的存储设备/cache.png" alt="image"><br>我们的低层设备分为了16个块,以0~15编号,我们的高层缓存只有4个块。可以看到我们只缓存了低层的4,9,2,15这几个块的数据。当程序需要访问低层的4号数据块的时候,由于缓存已经有了4号块,所以可以直接加载到CPU的寄存器中,而不是访问慢速的存储设备。如果说想要的数据不再缓存当中,这个时候就会产生缓存不命中,想要的缓存替换策略就起作用了,它需要决定哪一块被替换成新的,比如说LRU。缓冲不命中也分为这么几种情况:</p>
<ul>
<li>冷不命中。一开始缓存中不缓存任何块,这个时候程序访问缓存肯定会出现不命中,这种不命中就称为冷不命中。</li>
<li>冲突不命中。底层设备块i总是存放在缓存的编号为<code>i mod k</code>块中,例如我们图中的0,4,8,12块总是放在缓存的0号块,1,5,9,13块放在缓存的1号块。因此如果程序请求0,然后请求4,然后又请求0,这样导致的不命中就叫做冲突不命中。</li>
<li>容量不命中。有时候我们的缓存太小,实际程序需要访问的数据超过这个容量,这个时候就会产生不命中,这种不命中被称为容量不命中。</li>
</ul>
<h2 id="高速缓存的组织结构"><a href="#高速缓存的组织结构" class="headerlink" title="高速缓存的组织结构"></a>高速缓存的组织结构</h2><p>计算机中存储器地址都有$m$位,也就是$M=2^m$个不同的地址,高速缓存也不例外。此外,高速缓存还被分组管理,每组又可以分为好几行,一行中存放具体的缓存数据。如下图所示:<br><img src="/2017/11/10/计算机中的存储设备/cache-structure.png" alt="image"><br>高速缓存中每一行被称为缓存行,每一行会用一个比特位来标记该行数据是否有效,用$t$位来标记辅助判断数据是否在缓存行中,以及剩下$b$位用来存放数据。与之相对应的便是内存地址也被划分成了三块:<br><img src="/2017/11/10/计算机中的存储设备/addr-divide.png" alt="image"><br>标记位和缓存行的标记为都为$t$位,只有当标记位一致时说明数据在当前的缓存行内,组索引是用来确定当前地址的数据是被放在哪个组中的,行的确定是遍历找到的,最后块偏移就是用于确定数据在哪一个块的。上面的这种结构被称为高速缓存的通用结构,根据组内行的划分和组的划分还可以有三种不同的结构:</p>
<ul>
<li>直接相连结构。每组只有一行缓存行,这种好处是组内无需遍历行来寻找数据,但是很容易造成冲突不命中。</li>
<li>组相连结构。和通用结构一致,能有效介绍冲突不命中的概率,但是需要额外的缓存替换策略。</li>
<li>全相连结构。缓存只有一组,这一组包含了所有的行,这种方式对于行遍历的性能要求极高,成本也是最高的一种。</li>
</ul>
<p>到这里我们就大致讲解了一下什么是缓存,以及在高速缓存的结构。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>学习计算机中存储体系的知识,可以帮助我们在日后的编程的过程中,知道我们的数据是怎么在计算机中各个组件流动的,尤其是高速缓存的内容,当我们心中要高速缓存的知识的时候,便可以写出高速缓存友好的代码了,这种代码能够极好的提高程序的性能。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</div>
</article>
</section>
<nav class="pagination">
<span class="page-number current">1</span><a class="page-number" href="/page/2/">2</a><span class="space">…</span><a class="page-number" href="/page/8/">8</a><a class="extend next" rel="next" href="/page/2/"><i class="fa fa-angle-right"></i></a>
</nav>
</div>
</div>
<div class="sidebar-toggle">
<div class="sidebar-toggle-line-wrap">
<span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
<span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
<span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
</div>
</div>
<aside id="sidebar" class="sidebar">
<div class="sidebar-inner">
<section class="site-overview-wrap sidebar-panel sidebar-panel-active">
<div class="site-overview">
<div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
<p class="site-author-name" itemprop="name">Da Shen</p>
<p class="site-description motion-element" itemprop="description">Da Shen's blog</p>
</div>
<nav class="site-state motion-element">
<div class="site-state-item site-state-posts">
<a href="/archives/">
<span class="site-state-item-count">22</span>
<span class="site-state-item-name">日志</span>
</a>
</div>
<div class="site-state-item site-state-tags">
<span class="site-state-item-count">4</span>
<span class="site-state-item-name">标签</span>
</div>
</nav>
<div class="links-of-author motion-element">
</div>
</div>
</section>
</div>
</aside>
</div>
</main>
<footer id="footer" class="footer">
<div class="footer-inner">
<div class="copyright">© <span itemprop="copyrightYear">2017</span>
<span class="with-love">
<i class="fa fa-user"></i>
</span>
<span class="author" itemprop="copyrightHolder">Da Shen</span>
</div>
<div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>
<span class="post-meta-divider">|</span>
<div class="theme-info">主题 — <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Mist</a> v5.1.3</div>
</div>
</footer>
<div class="back-to-top">
<i class="fa fa-arrow-up"></i>
</div>
</div>
<script type="text/javascript">
if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
window.Promise = null;
}
</script>
<script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
<script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
<script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
<script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
<script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
<script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
<script type="text/javascript" src="/js/src/utils.js?v=5.1.3"></script>
<script type="text/javascript" src="/js/src/motion.js?v=5.1.3"></script>
<script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.3"></script>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
processEscapes: true,
skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
}
});
</script>
<script type="text/x-mathjax-config">
MathJax.Hub.Queue(function() {
var all = MathJax.Hub.getAllJax(), i;
for (i=0; i < all.length; i += 1) {
all[i].SourceElement().parentNode.className += ' has-jax';
}
});
</script>
<script type="text/javascript" src="//cdn.bootcss.com/mathjax/2.7.1/latest.js?config=TeX-AMS-MML_HTMLorMML"></script>
</body>
</html>