Shell's Home

上下文切换技术

Nov 20, 2014 - 1 minute read - Comments

上下文切换技术 简述 在进一步之前,让我们先回顾一下各种上下文切换技术。 不过首先说明一点术语。当我们说“上下文”的时候,指的是程序在执行中的一个状态。通常我们会用调用栈来表示这个状态——栈记载了每个调用层级执行到哪里,还有执行时的环境情况等所有有关的信息。 当我们说“上下文切换”的时候,表达的是一种从一个上下文切换到另一个上下文执行的技术。而“调度”指的是决定哪个上下文可以获得接下去的CPU时间的方法。 进程 进程是一种古老而典型的上下文系统,每个进程有独立的地址空间,资源句柄,他们互相之间不发生干扰。 每个进程在内核中会有一个数据结构进行描述,我们称其为进程描述符。这些描述符包含了系统管理进程所需的信息,并且放在一个叫做任务队列的队列里面。 很显然,当新建进程时,我们需要分配新的进程描述符,并且分配新的地址空间(和父地址空间的映射保持一致,但是两者同时进入COW状态)。这些过程需要一定的开销。 进程状态 忽略去linux内核复杂的状态转移表,我们实际上可以把进程状态归结为三个最主要的状态:就绪态,运行态,睡眠态。这就是任何一本系统书上都有的三态转换图。 就绪和执行可以互相转换,基本这就是调度的过程。而当执行态程序需要等待某些条件(最典型就是IO)时,就会陷入睡眠态。而条件达成后,一般会自动进入就绪。 阻塞 当进程需要在某个文件句柄上做IO,这个fd又没有数据给他的时候,就会发生阻塞。具体来说,就是记录XX进程阻塞在了XX fd上,然后将进程标记为睡眠态,并调度出去。当fd上有数据时(例如对端发送的数据到达),就会唤醒阻塞在fd上的进程。进程会随后进入就绪队列,等待合适的时间被调度。 阻塞后的唤醒也是一个很有意思的话题。当多个上下文阻塞在一个fd上(虽然不多见,但是后面可以看到一个例子),而且fd就绪时,应该唤醒多少个上下文呢?传统上应当唤醒所有上下文,因为如果仅唤醒一个,而这个上下文又不能消费所有数据时,就会使得其他上下文处于无谓的死锁中。 但是有个著名的例子——accept,也是使用读就绪来表示收到的。如果试图用多个线程来accept会发生什么?当有新连接时,所有上下文都会就绪,但是只有第一个可以实际获得fd,其他的被调度后又立刻阻塞。这就是惊群问题thundering herd problem。 现代linux内核已经解决了这个问题,方法惊人的简单——accept方法加锁。(inet_connection_sock.c:inet_csk_wait_for_connect) 线程 线程是一种轻量进程,实际上在linux内核中,两者几乎没有差别,除了一点——线程并不产生新的地址空间和资源描述符表,而是复用父进程的。 但是无论如何,线程的调度和进程一样,必须陷入内核态。 传统网络服务模型 进程模型 为每个客户分配一个进程。优点是业务隔离,在一个进程中出现的错误不至于影响整个系统,甚至其他进程。Oracle传统上就是进程模型。 缺点是进程的分配和释放有非常高的成本。因此Oracle需要连接池来保持连接减少新建和释放,同时尽量复用连接而不是随意的新建连接。 线程模型 为每客户分配一个线程。优点是更轻量,建立和释放速度更快,而且多个上下文间的通讯速度非常快。 缺点是一个线程出现问题容易将整个系统搞崩溃。 一个例子 py_http_fork_thread.py 在这个例子中,线程模式和进程模式可以轻易的互换。 如何工作的 父进程监听服务端口 在有新连接建立的时候,父进程执行fork,产生一个子进程副本 如果子进程需要的话,可以exec(例如CGI) 父进程执行(理论上应当先执行子进程,因为exec执行的快可以避免COW)到accept后,发生阻塞 上下文调度,内核调度器选择下一个上下文,如无意外,应当就是刚刚派生的子进程 子进程进程进入读取处理状态,阻塞在read调用上,所有上下文均进入睡眠态 随着SYN或者数据报文到来,CPU会唤醒对应fd上阻塞的上下文(wait_queue),切换到就绪态,并加入调度队列 上下文继续执行到下一个阻塞调用,或者因为时间片耗尽被挂起 关于更多细节,可以看这里。这篇文章里还介绍了epoll的工作细节。 评价 同步模型,编写自然,每个上下文可以当作其他上下文不存在一样的操作,每次读取数据可以当作必然能读取到。 进程模型自然的隔离了连接。即使程序复杂且易崩溃,也只影响一个连接而不是在整个系统。 生成和释放开销很大(效率测试的进程fork和线程模式开销测试),需要考虑复用。 进程模式的多客户通讯比较麻烦,尤其在共享大量数据的时候。 C10K问题 描述 当同时连接数在10K左右时,传统模型就不再适用。实际上在效率测试报告的线程切换开销一节可以看到,超过1K后性能就差的一塌糊涂了。 更细节描述,可以看这里。 进程模型的问题 在C10K的时候,启动和关闭这么多进程是不可接受的开销。事实上单纯的进程fork模型在C1K时就应当抛弃了。 Apache的prefork模型,是使用预先分配(pre)的进程池。这些进程是被复用的。但即便是复用,本文所描述的很多问题仍不可避免。 线程模式的问题 从任何测试都可以表明,线程模式比进程模式更耐久一些,性能更好。但是在面对C10K还是力不从心的。问题是,线程模式的问题出在哪里呢? 内存? 有些人可能认为线程模型的失败首先在于内存。如果你这么认为,一定是因为你查阅了非常老的资料,并且没仔细思考过。 你可能看到资料说,一个线程栈会消耗8M内存(linux默认值,ulimit可以看到),512个线程栈就会消耗4G内存,而10K个线程就是80G。所以首先要考虑调整栈深度,并考虑爆栈问题。 听起来很有道理,问题是——linux的栈是通过缺页来分配内存的(How does stack allocation work in Linux?),不是所有栈地址空间都分配了内存。因此,8M是*最大*消耗,实际的内存消耗只会略大于实际需要的内存(内部损耗,每个在4k以内)。但是内存一旦被分配,就很难回收(除非线程结束),这是线程模式的缺陷。 这个问题提出的前提是,32位下地址空间有限。虽然10K个线程不一定会耗尽内存,但是512个线程一定会耗尽地址空间。然而这个问题对于目前已经成为主流的64位系统来说根本不存在。 内核陷入开销? 所谓内核陷入开销,就是指CPU从非特权转向特权,并且做输入检查的一些开销。这些开销在不同的系统上差异很大。

上下文切换测试总结报告

Nov 18, 2014 - 2 minute read - Comments

效率测试 测试环境 Intel® Pentium® CPU G2030 @ 3.00GHz 8G内存 debian jessie Linux 3.16-2-amd64 2014年10月27日 附注一下,该CPU有2核心,无HT,1ns3个时钟周期。 测试方法 测试代码如下: time -f "%e,%S,%c,%r,%s,%K,%P" ./perf_fork 数据的意义分别为: 总时间,内核CPU时间,context switch次数,读/写次数,内存耗用,CPU使用百分比。 数据处理方法如下: import numpy as np p = lambda s: [float(line.strip().split(',')[0]) for line in s.splitlines()] q = lambda s: [float(line.strip().split(',')[1]) for line in s.splitlines()] np.array(p(s)).mean() np.array(p(s)).var() np.array(q(s)).mean() np.array(q(s)).var() 基础开销测试 函数调用开销 使用s_call来测试性能,循环1G次。 2.35,0.00,17,0,0,0,99% 2.34,0.00,13,0,0,0,99% 2.34,0.00,10,0,0,0,100% 2.35,0.00,10,0,0,0,99% 2.34,0.00,14,0,0,0,99% 2.34,0.00,6,0,0,0,99% 统计结果如下: time mean = 2.34 time var = 0.000022 每次call的开销为2.34ns,约7个指令周期。当然,这些并没有考虑调用压栈和数据返回。

手机上app的权限对比和分析

Nov 7, 2014 - 2 minute read - Comments

简述 今天看到这篇文章,勾起了我的好奇。我的手机里有多少app有安全隐患呢?当然,我知道很多有安全问题的app我不能删——例如企鹅家。但是如果某个app没有必然需要,或者有替代品。我不介意换一个用。所以我写了这篇blog,对比了各种同类或近似app的权限要求。 先说明一点,这篇文章所列出的app权限,是根据当前(2014-11-07)google play上的应用权限数据,截取我感兴趣的部分权限汇总的。既不是完全的敏感权限列表,也不可能不变化。如果有什么补充,欢迎你联系我。 同时你需要知道,app需要某个权限,并不一定表示要用来做坏事。很多时候是因为功能确实需要。因此我也在下面点评了部分我知道的功能需要对应权限。即便我们在app里面找不到权限对应功能,也不能绝对断言app正在作恶——只是相比起来我更信任不需要提供这个权限的app而已。这也是为什么我做的是分类对比——方便你来比较和替换。 IM类 telegram read SMS 照相 录音 location read/modify contacts run at startup talkback change audio setting 我太感动了。 QQ read SMS read/modify contacts 照相 录音 location read/write call log read/write calendar disable screen lock NFC bluetooth run at startup change audio setting 流氓权限大集合啊。你说要contacts我还能理解,要call log干嘛?还要write?而且功能里也找不到为什么需要NFC和蓝牙。 微信 read SMS 照相 录音 location read/modify contacts bluetooth run at startup change audio setting 基本同QQ,不过权限少了很多。 评论 虽然我们都知道talkback比企鹅家的对隐私更友好,可是有本事你别用企鹅家。。。 SNS facebook read sms

context切换测试——C语言协程有关部分请求review

Nov 6, 2014 - 1 minute read - Comments

setjmp/longjmp测试 使用s_jmp程序来测试setjmp的性能。1G次循环。下面是结论: 5.77,0.00,29,0,0,0,99% 5.70,0.00,30,0,0,0,99% 5.71,0.00,22,0,0,0,99% 5.71,0.00,23,0,0,0,99% 5.70,0.00,30,0,0,0,100% 5.70,0.00,23,0,0,0,99% 统计结果如下: time mean = 5.715 time var = 0.000625 单次调度开销只有5.7ns,在所有测试中性能最优。(glibc-2.19/sysdeps/x86_64/setjmp.S) getcontext/setcontext测试 使用s_context程序来测试setcontext的性能。100M次循环。下面是结论: 12.96,5.88,79,0,0,0,99% 13.13,5.94,105,0,0,0,99% 12.95,6.18,57,0,0,0,99% 13.13,5.90,64,0,0,0,99% 12.95,5.88,82,0,0,0,99% 12.96,5.80,51,0,0,0,99% 统计结果如下: time mean = 13.01 time var = 0.0068 单次调度开销高达130ns,仅比系统的sched在高线程下略快。这事很奇怪,因为根据我看到的源码(glibc-2.19/sysdeps/unix/sysv/linux/x86_64/setcontext.S),getcontext/setcontext在glibc中是用汇编实现的。其中陷入内核只是为了设定signal mask。

context切换测试——线程创建有关部分请求review

Oct 31, 2014 - 1 minute read - Comments

线程模式开销 使用t_thread程序,循环1M次,重复6次,原始数据如下: 9.57,8.22,21098,0,0,0,104% 9.77,8.40,29704,0,0,0,104% 9.36,8.17,10390,0,0,0,106% 9.56,8.50,14514,0,0,0,107% 9.35,8.34,7244,0,0,0,108% 9.57,8.43,26351,0,0,0,106% 统计结果如下: time mean = 9.53 time var = 0.02 kernel mean = 8.34 kernel var = 0.013 解读数据可以看到,thread模式的开销为9530ns(已经降到纳秒级了),CPU将为8340ns,精确级别在20ns级别。粗略换算一下每次create的开销大约是30k个时钟周期。简单对比可以看出,thread模式比fork模式大约快了5倍。

context切换测试——调用开销有关部分请求review

Oct 30, 2014 - 1 minute read - Comments

函数调用开销 使用s_call来测试性能,循环1G次。 2.35,0.00,17,0,0,0,99% 2.34,0.00,13,0,0,0,99% 2.34,0.00,10,0,0,0,100% 2.35,0.00,10,0,0,0,99% 2.34,0.00,14,0,0,0,99% 2.34,0.00,6,0,0,0,99% 统计结果如下: time mean = 2.34 time var = 0.000022 每次call的开销为2.34ns,约7个指令周期。当然,这些并没有考虑调用压栈和数据返回。 内核调用开销 使用s_syscall来测试性能,循环1G次。这里特意选用了一个不可能失败的内核函数,getpid,来衡量每次进入getpid的开销。 4.37,0.00,76,0,0,0,99% 4.34,0.00,43,0,0,0,99% 4.37,0.00,124,0,0,0,99% 4.37,0.00,63,0,0,0,99% 4.36,0.00,48,0,0,0,99% 4.36,0.00,47,0,0,0,99% 统计结果如下: time mean = 4.36 time var = 0.00011 这里可以看到,纯粹的内核进入开销小到非常惊人,只有4.36ns,约合13个指令周期,而且这里还要进行数据的查询和返回。所以内核调用开销在下面的测试中全部忽略不计。

context切换测试——python有关部分请求review

Oct 29, 2014 - 1 minute read - Comments

python yield模式性能测试 python下的测试就不用time了,我们改用python的timeit,循环100M次。具体可以看py_yield.py。数据结果如下: 7.64262938499 9.2919304393e-06 5.41777145863 4.94284924931e-06 从结果来看,100M次循环的平均时间是5.4s,平均每次大约54ns。使用yield后变为76ns,增加了22ns。 python greenlet模式性能测试 这次代码在py_greenlet.py,循环10M次。数据结果如下: 5.35270996888 7.44085846125e-05 5.31448976199 5.82336765673e-05 单次循环时间消耗为535ns。比最初的54ns,增加了481ns。基本来说,时间增长了10倍率。 这是预料中的,因为greenlet早就声明自己通过堆栈拷贝来实现上下文切换。这会消耗大量CPU时间。从原理上说,栈越深,消耗越大。但是测试结果表明两者几乎没有差异,栈深反而性能更加优异(TODO: why?)。

context切换测试——进程有关部分请求review

Oct 28, 2014 - 1 minute read - Comments

测试环境 Intel® Pentium® CPU G2030 @ 3.00GHz 8G内存 debian jessie Linux 3.16-2-amd64 2014年10月27日 附注一下,该CPU有2核心,无HT,1ns3个时钟周期。 测试方法 测试代码如下: time -f "%e,%S,%c,%r,%s,%K,%P" ./perf_fork 数据的意义分别为: 总时间,占用CPU时间,context switch次数,读/写次数,内存耗用,CPU使用百分比。 数据处理方法如下: import numpy as np p = lambda s: [float(line.strip().split(',')[0]) for line in s.splitlines()] q = lambda s: [float(line.strip().split(',')[1]) for line in s.splitlines()] np.array(p(s)).mean() np.array(p(s)).var() np.array(q(s)).mean() np.array(q(s)).var() 进程fork开销 使用s_fork程序(注释语句关闭模式),粒度1M次,重复6次,原始数据如下: 49.04,26.83,29784,0,0,0,55% 51.53,26.38,32057,0,0,0,52% 49.88,26.02,30892,0,0,0,53% 51.39,27.13,37573,0,0,0,54% 52.89,28.12,37924,0,0,0,54% 51.19,27.02,35880,0,0,0,54% 统计结果如下: time mean = 50.98 time var = 1.52 cpu mean =

bash严重漏洞

Sep 25, 2014 - 1 minute read - Comments

今天估计各大消息都在报这个漏洞,可能有些人看到有修复就放松了。目前来看,事情没那么简单。 CVE-2014-6271 第一个漏洞,编号为CVE-2014-6271。相应的dsa和usn。 具体的文章可以看这里。 简单来说,当bash执行时看到有变量定义了一个函数,函数尾部又剩余了部分代码。会直接把剩余代码执行了。导致简单的变量定义动作有机会执行任意代码。 对于未修补的系统,执行以下代码出现以下提示: $ env x='() { :;}; echo vulnerable' bash -c "echo this is a test" vulnerable this is a test 注意echo vulnerable应当不被执行的。 修复的系统则是以下表现: $ env x='() { :;}; echo vulnerable' bash -c "echo this is a test" bash: warning: x: ignoring function definition attempt bash: error importing function definition for `x' this is a test CVE-2014-7169 第二个漏洞,编号为CVE-2014-7169。 这个漏洞是第一个漏洞没有修复完全导致的,最麻烦的是,这个漏洞没有修复,细节却满天飞了。 表现如下: $ env X='() { (a)=>\' sh

送书

Sep 4, 2014 - 1 minute read - Comments

最近清理家中藏书,打算送掉一部分纸质书。于是顺手列了一批自己需要,但是可以外借的书。 送人的部分,如果你需要,两个月内联系我,自己想办法拿走(例如H4,或者直接上门)。逾期我就直接送去资源回收了。借阅的部分,记得好好保管图书,不管借阅多久,记得回头还我。 送人 delphi 5.0 开发多媒体应用 ISBN 7-5084-0470-X C++语言程序设计 ISBN 7-302-03926-7 计算机绘图 ISBN 7-313-01482-1/TP.273 borland C++ windows程序设计 ISBN 7-115-05265-4/TP.117 最新C++语言精华 ISBN 7-5053-3975-3/TP.1731 C++语言程序设计 ISBN 7-302-04504-6 C++使用手册 ISBN 7-5053-2890-5/TP.959 microsoft visual C++ .net技术内幕 ISBN 7-302-08931-0 操作系统教程 ISBN 7-04-012664-8 借阅 模式识别 ISBN 7-121-02647-3 深入解析Windows操作系统 ISBN 978-7-121-03969-0 计算机网络 ISBN 7-302-08977-9 深入浅出MFC ISBN 7-900614-93-1 Python源码解析 ISBN 978-7-121-06874-4 数据结构与算法(Java语言版) ISBN 7-111-11902-9 网络渗透技术 ISBN 7-121-01035-6 密码编码学与网络安全 ISBN 7-5053-9395-2 人工智能及其应用 ISBN 7-302-02127-9 神经网络原理 ISBN 7-111-12759-5