-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathcontext_slide.html
425 lines (371 loc) · 14 KB
/
context_slide.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
<!doctype html>
<html lang="en">
<head>
<title>context_slide</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="stylesheet" href="http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/css/reveal.css">
<link rel="stylesheet" href="http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/css/theme/default.css" id="theme">
<!-- For syntax highlighting -->
<link rel="stylesheet" href="http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/lib/css/zenburn.css">
<!-- Include the appropriate print stylesheet -->
</head>
<body>
<div class="reveal">
<div class="slides">
<section>
<section>
<h1>上下文切换技术简介</h1>
</section>
<section>
<h2>logo</h2>
<p><img src="PyConChina2014-stick-logo.png" alt="PyConChina2014-stick-logo.png" title="" /></p>
</section>
<section>
<h2>自我介绍</h2>
<p>shell909090,七牛程序员,主要用python和golang。</p>
</section>
<section>
<h2>LICENSE</h2>
<p><a href="http://creativecommons.org/licenses/by-sa/3.0/deed.zh">cc-by-sa3.0</a></p>
</section>
</section>
</div>
<div class="slides">
<section>
<section>
<h1>系统级上下文技术</h1>
</section>
<section>
<h2>术语</h2>
<ul>
<li>上下文
<ul>
<li>调用栈</li>
<li>指向栈的寄存器</li>
</ul></li>
<li>切换
<ul>
<li>当前执行的一个上下文变为另一个上下文</li>
<li>不是上下文的创建和消灭,而是上下文获得CPU时间</li>
</ul></li>
<li>调度
<ul>
<li>决定哪个上下文应当获得CPU了</li>
</ul></li>
<li>进程
<ul>
<li>分配资源的单位</li>
</ul></li>
<li>线程
<ul>
<li>分配CPU的单位</li>
</ul></li>
</ul>
</section>
<section>
<h2>进程状态</h2>
<ul>
<li>运行: 得到CPU</li>
<li>就绪: 可以得到CPU,但是尚未调度到</li>
<li>睡眠: 等待某些条件,因此获得了CPU也没用</li>
</ul>
</section>
<section>
<h2>性能</h2>
<ul>
<li>fork的开销在40-50us不等</li>
<li>pthread(nptl)的开销在9.5us左右</li>
</ul>
</section>
</section>
</div>
<div class="slides">
<section>
<section>
<h1>传统网络服务模型</h1>
</section>
<section>
<h2>如何工作</h2>
<ol>
<li>父进程监听服务端口</li>
<li>在有新连接建立的时候,父进程执行fork,产生一个子进程副本</li>
<li>如果子进程需要的话,可以exec(例如CGI)</li>
<li>父进程执行到accept后,发生阻塞</li>
<li>上下文调度,内核调度器选择下一个上下文,一般就是刚派生的子进程</li>
<li>子进程进程进入读取处理状态,阻塞在read调用上,所有上下文均进入睡眠态</li>
<li>随着SYN或者数据报文到来,CPU会唤醒对应fd上阻塞的上下文</li>
<li>上下文切换到就绪态,并加入调度队列</li>
<li>继续执行到下一个阻塞调用,或者因为时间片耗尽被挂起</li>
</ol>
</section>
<section>
<h2>fork的细节</h2>
<p>产生当前进程的同等副本,在fork时并不立刻分配新的资源。</p>
<p>而是在其中某个进程执行了写入时发生复制,这叫做COW。</p>
<p>理论上fork应当先执行子进程,因为有很大可能执行exec。</p>
<p>而exec执行的快可以避免COW。</p>
</section>
<section>
<h2>阻塞-调度的细节</h2>
<ul>
<li>当试图读取数据而无数据可读时,既不能得到数据也不能返回。</li>
<li>此时上下文处于等待某些条件的状态。</li>
<li>在此种条件下,上下文不参与调度。系统会自动选择一个就绪的上下文去切换。</li>
<li><p>当前上下文被挂入等待条件的wait_queue队列。</p></li>
<li><p>当条件就绪,例如收到网络数据的时候,会唤醒wait_queue上挂起的所有上下文。</p></li>
<li>不能只唤醒一个。</li>
<li>因为如果一个上下文不能消费所有数据,会使得剩余上下文无谓阻塞。</li>
<li>但是如果数据只够一个上下文消费,会发生惊群。</li>
</ul>
</section>
<section>
<h2>评价</h2>
<ul>
<li>同步模型编写自然,每次读取数据可以当作必然能读取到。</li>
<li>进程模型隔离性好,每个上下文可以当作其他上下文不存在一样的操作。</li>
<li>即使程序复杂且易崩溃,也只影响一个连接而不是在整个系统。</li>
<li>生成和释放开销很大,需要考虑复用。</li>
<li>进程模式的多客户通讯比较麻烦,尤其在共享大量数据的时候。</li>
</ul>
</section>
</section>
</div>
<div class="slides">
<section>
<section>
<h1>C10K问题</h1>
</section>
<section>
<h2>上下文重复建立开销</h2>
<p>通过上下文池和复用上下文解决。</p>
<p>上下文不销毁,而是从头开始循环,获得下一个请求并处理。</p>
</section>
<section>
<h2>线程复用模式的问题</h2>
<p>线程复用模式仍然不够快,往往会被认为以下两个因素。</p>
<ul>
<li>内存</li>
<li>内核陷入</li>
</ul>
</section>
<section>
<h2>linux栈分配原理</h2>
<p>linux下只为栈保留地址空间,而不映射内存。</p>
<p>当地址空间首次被访问时,才分配物理内存。因此大量线程不会消耗无谓内存。</p>
<p>8M是最大内存限制。</p>
<p>当栈回退时,物理页面映射不释放。</p>
</section>
<section>
<h2>内核陷入开销</h2>
<p>很小,最低只有50ns。而普通函数调用也有2ns。</p>
<p>而且现代高效模型也要通过read陷入来获得数据,这个开销未能避免。</p>
</section>
<section>
<h2>内核调度器</h2>
<ol>
<li>linux2.4的调度器。</li>
<li>O(1)调度器。</li>
<li>CFS。</li>
</ol>
</section>
<section>
<h2>开销</h2>
<p>yield每次耗费的时间随活跃线程数变化曲线</p>
<p><img src="t_yield.png" alt="t_yield.png" title="" /></p>
</section>
<section>
<p>lock每次耗费的时间随活跃线程书变化曲线</p>
<p><img src="t_lock.png" alt="t_lock.png" title="" /></p>
</section>
<section>
<h2>lock消耗为什么不随着活跃线程数上升</h2>
<p>因为lock调用futex,而将线程至于睡眠态。</p>
</section>
</section>
</div>
<div class="slides">
<section>
<section>
<h1>多路复用</h1>
</section>
<section>
<h2>简述</h2>
<ul>
<li>要突破C10K,就要减少活跃上下文数。因此一个上下文必须能处理多个连接。</li>
<li>因此系统调用时必须立刻返回。否则会塞住上下文,阻碍复用。</li>
<li>非阻塞调用可以做到立刻返回,但是如何选择要读的文件句柄?</li>
</ul>
</section>
<section>
<h2>就绪通知 vs 异步IO</h2>
<ul>
<li>由用户读取 vs 由系统回调</li>
<li>在数据就绪时通知 vs 在数据IO完成后回调</li>
</ul>
</section>
<section>
<h2>linux的就绪通知发展</h2>
<ul>
<li>select: 每次调用需要传入fd数组,有最大限制</li>
<li>poll: 每次调用需要传入fd数组,无最大限制</li>
<li>epoll: 每次调用无需传入数组,没有最大限制</li>
</ul>
</section>
<section>
<h2>epoll</h2>
<ul>
<li>ET: 状态转变时触发</li>
<li>LT: 读取所有就绪句柄</li>
</ul>
</section>
<section>
<h2>性能分析</h2>
<ul>
<li>epoll_wait/ep_poll_callback: O(1)级</li>
<li>epoll_ctl: O(logn)级</li>
</ul>
</section>
<section>
<h2>固有缺陷</h2>
<p>无法用于普通文件。</p>
</section>
</section>
</div>
<div class="slides">
<section>
<section>
<h1>事件通知机制下的程序设计模型</h1>
</section>
<section>
<h2>用户态调度</h2>
<p>知道了哪些fd就绪,如何处理这些fd?</p>
<p>将对应fd映射到处理这个fd的某组过程上的结构设计,被称为用户态调度。</p>
<p>这样,当我们知道某个fd就绪时,可以激活对应的过程。</p>
<p>更广义的说,各种内核态带有wait_queue的对象,在用户态调度上都要做类似处理。</p>
<p>例如时钟和锁。</p>
</section>
<section>
<p>因此用户态调度可类比于内核态调度,但是用户态调度没有抢占。</p>
<p>当这个过程长期执行不返回时,其他句柄不能被及时处理。</p>
<p>用公平性换取效率。</p>
</section>
<section>
<h2>coroutine</h2>
<p>切换机器状态字和执行栈不需要进入内核,这也是用户态上下文切换的核心思想。</p>
<p>setjmp/longjmp只能切换状态字,而没有独立的执行栈。</p>
<p>因此setjmp/longjmp用于coroutine总是有点问题,建议用ucontext。</p>
<p>greenlet也是利用新建独立执行栈和替换当前栈顶部frame的方法来切换。</p>
</section>
<section>
<h2>协程和线程的关系和区别</h2>
<ul>
<li>协程要获得CPU,必须在线程中执行</li>
<li>协程无法跨进程调度</li>
<li>同时执行的协程数不能大于容纳他的线程数</li>
<li>协程没有抢占</li>
</ul>
<p>因此,单线程中执行的协程,可以视为单线程应用。</p>
<p>在阻塞调用之间,访问数据对象时是不需要加锁的。</p>
</section>
<section>
<h2>golang协程性能</h2>
<p>sched调用延迟随goroutine数变化</p>
<p><img src="g_sched.png" alt="g_sched.png" title="" /></p>
</section>
<section>
<p>chan调用延迟随goroutine数变化</p>
<p><img src="g_chan.png" alt="g_chan.png" title="" /></p>
</section>
<section>
<p>lock调用延迟随goroutine数变化</p>
<p><img src="g_lock.png" alt="g_lock.png" title="" /></p>
</section>
<section>
<h2>python协程性能</h2>
<ul>
<li>yield的开销大约是22ns</li>
<li>greenlet的开销大约是500ns</li>
</ul>
</section>
<section>
<p>yield from随层数变化</p>
<p><img src="py_yieldfrom.png" alt="py_yieldfrom.png" title="" /></p>
</section>
<section>
<h2>基于就绪通知的协程框架</h2>
<ol>
<li>包装read/write,检查返回。</li>
<li>如果是EAGAIN,将当前协程绑定到fd上,然后执行调度函数。</li>
<li>调度函数epoll,读一个就绪的fd。如果没有,阻塞直到至少一个fd就绪。</li>
<li>查找这个fd对应的协程上下文对象,并切换过去。</li>
<li>当某个协程被切换到时,应当再重试读取,失败的话重复2。</li>
<li>如果读取到数据了,返回。</li>
</ol>
</section>
<section>
<p>这样,异步的数据读写动作,在我们的想像中就可以变为同步的。</p>
<p>而同步模型会极大降低我们的编程负担。</p>
</section>
<section>
<h2>回调模型</h2>
<p>所谓回调模型就是,在IO调用的时候,同时传入一个函数,作为返回函数。</p>
<p>当IO结束时,调用传入的函数来处理下面的流程。</p>
</section>
<section>
<h2>CPS</h2>
<p>用一句话来描述CPS——他把一切操作都当作了IO。</p>
<p>无论干什么,结果要通过回调函数来返回。</p>
<p>从这个角度来说,IO回调模型只能被视作CPS的一个特例。</p>
<pre><code>add = lambda f, *nums: f(sum(nums))
mul = lambda f, *nums: f(reduce(lambda x,y: x*y, nums))
mul(lambda x: add(pprint.pprint, x, 1), 2, 3)
</code></pre>
</section>
<section>
<h2>函数组件和返回值</h2>
<p>函数为什么要用栈来描述层级调用关系?</p>
<ul>
<li>因为函数必须等待返回值或者同步顺序。</li>
<li>因此调用者的状态需要被保存,换入被调用者执行,直到返回时再换回。</li>
<li>这个角度来说,调用是最朴素而原始的上下文切换手段。</li>
<li>无需同步的调用可以用mq来解藕。</li>
<li>而CPS则象征另一个极端。函数的返回值可以不返回调用者,而是第三者。</li>
</ul>
</section>
</section>
</div>
<div class="slides">
<section>
<h1>Q&A</h1>
</section>
</div>
</div>
<script src="http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/lib/js/head.min.js"></script>
<script src="http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/js/reveal.js"></script>
<script>
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
transition: Reveal.getQueryHash().transition || 'default', // none/fade/slide/convex/concave/zoom
// Parallax scrolling
// parallaxBackgroundImage: 'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg',
// parallaxBackgroundSize: '2100px 900px',
// Optional libraries used to extend on reveal.js
dependencies: [
{ src: 'http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: 'http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: 'http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
{ src: 'http://xuzhixiang.u.qiniudn.com/reveal/2.6.2/plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
]
});
</script>
</body>
</html>