linux 信号处理机制简介

鉴于后面把进程的形象给彻底毁掉了,我提前声明一下,进程是有尊严的有节操的,当然大部分人可能也看不到毁形象那一段。为什么介绍linux要从信号开始呢,当然是为了保证能讲明白,因为翻了翻书我发现这一部分是最简单的了,所以呢,就讲这个吧,顺便把之前源码阅读的东西总结一下。

信号是什么东西呢?

两个直观的感受,你在终端运行一个程序然后摁一下Ctrl+c就是向正在运行的程序发送了一个终止信号,程序就被终止了;在终端kill一个pid相当于发送9号杀死这个进程;在终端运行kill -l 就可以查看系统的所有信号。

有了上面这些直观感受,那么信号本质是什么呢?信号本质上是一种向一个进程通知发生异步事件的机制,是在软件层次上对中断的一种模拟。这种通知机制可以用于通知硬件消息like上面的感受1,也可以用来进行进程间通信like上面的感受2,还可以用来通知一些程序错误如除0、非法内存访问。异步是说进程没有对信号进行实时监控,不必等待信号到来,事实上进程也根本不知道信号什么时候会来。一个进程本来在欢乐的跑着突然就被你一个ctrl+c给杀死了,飞来横祸呀。至于说是一种软中断,是因为在原理上,一个进程受到一个信号与处理器收到一个中断请求可以说是一样的,本来在欢乐的跑着就从你一个脚上给你来一个高电平。


信号分类

通过kill -l可以看到linux现在支持64个信号,注意一下信号不是从0编号的而是从1编号。其中前32为标准(Standard)信号,后32为实时(Real-time)信号。好吧,什么是标准信号,什么又是实时信号。

在遥远的古代是只有标准信号的,那时候它也不叫标准信号,就叫信号,它是一种十分简单的机制。先说一下信号的运行,当信号发送到程序时并不是立即执行而是等待某个时机再执行,在这个时机还没到来的时候你一个类型的信号无论发多少个都只记录一个,就好比有32个信箱每个信箱只能收一封信,多的就扔吧;另一方面信号的响应也是不保证顺序的,你发送信号的顺序和信号响应的顺序可能根本就没什么关系,因为古代人类都比较简单嘛。后来人类不断发展又想要可以响应一个类型的多个信号又想保证响应顺序,实时信号就诞生了,其实就是加了个sigqueue这么个队列数据结构,需求就被满足了。但是之前的简单信号已经成为了实际上的标准,而实时信号的应用也还不如前者广,两者就共存了。

Tips:实时信号的信号范围由SIGRTMIN和SIGRTMAX两个宏来决定,编程使用实时信号的话可以使用SIGRTMIN+n指定一个信号而不是直接一个数字,因为万一标准信号数量又增加呢,直接写数字编码可能就会出现bug,这两个宏也为将来信号的灵活扩展提供了基础。同时也是灌输一个编程不要使用魔数的原则。

信号响应

UNIX对前32个信号都有默认的响应方式,分为以下5类:
  • Term:终止进程
  • Ign:忽略该信号
  • Core:终止进程并保存内存信息
  • Stop:停止进程
  • Cont:有停止就有恢复进程

当然只有5个响应方法怎么够呢,not fashion 于是sigaction()这个系统调用就上了,通过它可以给一个信号绑定一个函数来当作信号处理函数,你就可以在这个函数里面胡作非为了。可是你胡作非为了内核开发人员又感觉不爽了于是就设了两个信号你是改不了的,以显示他们不可动摇的地位,这两个信号就是9号SIGKILL和19号SIGSTOP,所以你也就不能定义Ctrl+c和Ctrl+z发送出来的信号的处理方式了。

Tips:当然你足够邪恶的话可以定义这两个组合键指向别的操作。

信号处理机制

废话这么多终于开始讲机制了。

信号发送和接收

最简单的理解,一个程序给另一个程序发了个短信,通过中国移不动或者中国联不通的网络,另一个程序的手机就收到了,一个信号就算发送成功了。具体来说就是一个程序调用一个发送信号的系统调用例如。然后内核就扮演运营商的角色把信号扔给另一个进程。我们知道进程在内存里还是有很多家当的,主要维护了一个进程描述符,里面有着pid呀,进程状态呀,优先级呀一堆不可告人的秘密,等以后有空了我给大家八卦一下。

pending 和 signal 是两个挂起信号队列,为什么要有两个呀?因为一个是私有的队列一个是共享的队列。为什么有私有和共享之分呀?因为一个是针对轻量级进程的一个是针对线程组的?这两个又是什么东西呀?本小农发现这里开始不好说了.为什么有私有和共享之分呀?因为一个是针对轻量级进程的一个是针对线程组的?这两个又是什么东西呀?本小农发现这里开始不好说了……简单说,Linux是没有进程和线程的,有的只有轻量级进程,如果一组轻量级进程之间可以共享资源,那么就组成一个相当于线程组的东西,也就相当于一个线程,换句话说Linux是用轻量级进程这个东西模拟多线程,感兴趣同学可以看一下LWP,总之这里知道有两个信号挂起队列就好了,如果前面LWP的东西没看懂,你这里可以认为一个是记录的给线程的信号,一个记录的给进程的信号。sighand就简单多了就是记录64个信号对应的处理函数的入口地址,当然还有其他好多辅助的数据结构,但主要就是这个功能。如果能大致看懂下面这张图说明你还没晕。

signal struct

回到手机短信,内核把短信发给进程是干了什么事呢?就是找个队列把信号插进去。当然进程也是有尊严的,不会让你随便插的如果是标准信号的话你只能插一次,如果这个信号还在的话就不让你插了,不像对实时信号那么随便想插多少插多少。有的进程比较专一,如果有一个信号插进来他会设置一个屏蔽位不让别的信号插,可以对比一下中断,处理器有时也会设一个中断屏蔽位有木有。

信号的查看与处理

短信发过来了,不一定就被看到了,这也是异步说的意思,那么什么时候进程才会发现我有一个新的短消息呢?原来进程是在从内核态这个黑暗的角落到用户态切换之前偷偷的看一眼短信,看看都有谁插了进来,然后把他们处理掉,再回到用户态光明正大的去接客。那么什么时候进程会去内核态这个小黑屋呢,主要有三种情况:

  • 执行系统调用
  • 处理中断异常
  • 进程调度上CPU

出小黑屋前,会看一眼手机,如果有短消息就啪啪啪的处理短信,如果没有的话就伤感的回用户态。如果是比较规矩的短信只是执行五种规定的标准动作那么在小黑屋解决就好了,但是有的短信比较坏调用了sigaction告诉进程要出来到这个地方来玩,然后进程就把手机扔小黑屋里拔腿就跑到用户态去玩了,玩完想起来手机还在家,就又回趟家看看还有没有其他短信,没有再去用户态光明正大的见人。具体过程如下图。

好了不调戏进程了,进程也是有尊严的,下面讨论一下进程的节操问题,节操这个问题确实比较难说,唉……

不管你知不知道,进程从用户态进入内核态是要再内核态保存一份用户态堆栈的副本的,其中最重要的就是保存当前的pc这样进程从内核态返回的时候把pc还原就可以按照原来的指令流行进了;不管你知不知道,当进程从内核态回到用户态的时候这个堆栈的副本是被清空的。于是当进程在收到一个出去玩的短信出去之后,他原来的用户态返回地址就被默默的清空了,然后他玩完回到小黑屋就发现找不到回用户态的路了,一辈子就被关在这个阴冷黑暗的小黑屋,永世不得见光,这个故事告诉我们节操是很重要的。然而这个恐怖的故事没有限制住任何一个进程寻欢作乐,进程们出去玩之前先往外发了个消息把用户态的返回地址,堆栈信息什么的都发出去了,等玩完回家再等小哥把信息发回来,就又可以光明正大的回用户态了,所以节操这个东西……

好了,上面都是为了加强理解的段子,下面是正儿八经的原理介绍,需要有对堆栈和函数调用机制有一些了解,你会发现还是节操比较好说。

我们知道,当进程陷入内核态的时候,会在堆栈中保存中断现场。因为用户态和内核态是两个运行级别,所以要使用两个不同的栈。当用户进程通过系统调用刚进入内核的时候,CPU会自动在该进程的内核栈上压入下图所示的内容:(图来自《Linux内核完全注释》)

在处理完系统调用以后,就要调用do_signal()函数进行设置frame等工作。这时内核堆栈的状态应该跟下图左半部分类似(系统调用将一些信息压入栈了):

在找到了信号处理函数之后,do_signal函数首先把内核堆栈中存放返回执行点的eip保存为old_eip,然后将eip替换为信号处理函数的地址,然后将内核中保存的“原ESP”(即用户态栈地址)减去一定的值,目的是扩大用户态的栈,然后将内核栈上的内容保存到用户栈上,这个过程就是设置frame.值得注意的是下面两点:

  1. 之所以把EIP的值设置成信号处理函数的地址,是因为一旦进程返回用户态,就要去执行信号处理程序,所以EIP要指向信号处理程序而不是原来应该执行的地址。
  2. 之所以要把frame从内核栈拷贝到用户栈,是因为进程从内核态返回用户态会清理这次调用所用到的内核栈(类似函数调用),内核栈又太小,不能单纯的在栈上保存另一个frame(想象一下嵌套信号处理),而我们需要EAX(系统调用返回值)、EIP这些信息以便执行完信号处理函数后能继续执行程序,所以把它们拷贝到用户态栈以保存起来。

以上这些搞清楚之后,下面的事情就顺利多了。这时进程返回用户空间,就会根据内核栈中的EIP值执行信号处理函数。那么,信号处理程序执行完后,怎么返回程序继续执行呢?

信号处理程序执行完毕之后,进程会主动调用sigreturn()系统调用再次回到内核,查看有没有其他信号需要处理,如果没有,这时内核就会做一些善后工作,将之前保存的frame恢复到内核栈,恢复eip的值为old_eip,然后返回用户空间,程序就能够继续执行。至此,内核遍完成了一次(或几次)信号处理工作。


参考资料

Published: 09 May 2012

我这阵子读的书-Top5

reading

如果书籍是人类进步的阶梯的话,那么kindle就是人类进步的电梯。自打发现kindle看书还是蛮舒服的,就开始了不可控制的刷书岁月。

之前定的计划是一年看完一千万字,不过后来觉得给读书这件事定个指标显得如此的功利,而且这个看似远大的指标如果真放开了去冲的话也…………不过之后又看到位大神四年的书单 我就沉默了。鉴于本小农最近打算看一些和种地相关的书,就把这几个月看过的感觉比较好的书拿出来晒晒。


美国种族简史

★★★★★

如果说只能推荐一本书的话,除了这本我暂时想不到其他的候选者了。老罗说大部分中国人都是隐性的种族主义者,我当时还嗤之以鼻我和谁种族主义去,现在看来这种隐性表现在不自知上。

严格来说这是一本蛮学术的历史书,和中国古典的历史书浓厚的文学色彩不同,老美的历史书显得专业且严谨。短短二百余年的历史却写出了读中国史所感觉不倒的跨度和纵深感。书中详细叙述了各个种族如何踏上这片土地,在极其艰苦的物质条件和当地居民的排斥的夹缝中不断成长,最后被认可和接纳的历史。从单个族群来看是一个又一个波澜壮阔的American dream,从整个美国民族来看则是各个种族在相互竞争乃至相互排斥的过程中不断相互接纳和融合创造一个个美国奇迹的史诗。

中美两国又很多相似的地方,都是地大物博民族众多,也都是经过八年抗战击败侵略者,又经历了南北战争实现了国家的统一,但是却走上了不同的发展道路。虽说中国有56个民族但是由于汉化的问题,很多民族的特质已经不是很明显了,然而在不同地域上居民的差异却十分显著,每个地方都有自己的文化和语言(我总感觉中国各地方言的差异比伦敦英语和印度英语差异还大),在美国很多种族之间的问题反而都能在中国的地域问题上有着投射。诸如各地对自己所在地的优越感,以及对外来户的排斥,跨个省定居,从欧洲的跨度来看也就算移民了,再有对于某些地方来的人所固有的刻板思维,比如说哪里的人都是骗子,哪里的人都是挖煤的,哪里人都是炒房的,还有哪里人连小孩都吃。

或许是中国固有的大一统思维,让我们对那些和自己不同的差异容忍度很低,总是轻易表现出对某个地域特质的一些轻视,不自觉的就干着一些种族主义者的事情还自觉心安理得。包括政府的一些行为也总是希望一个命令可以统一全国的步伐,希望以一致性来取代差异性,就好比推行普通话,而不希望让这些差异性的东西能够自由成长。其实在这么一个大国差异性是不可避免的,看一下西藏的情况再瞅一眼台湾你就会感觉怎么可能会让他们有着相同的步伐?或许我们应该多一些对差异的包容和尊重而不是妄自尊大。


上帝掷骰子吗

★★★★★

标题源自爱因斯坦关于量子力学的一个著名观点,即上帝是不会掷骰子的,然而按照现在的主流观点来看,上帝居然是个赌徒。这是一本超出想象好的书,有着很多意外的收获,也启迪着我思考了很多原来根本无法想象的东西,所以破例给个科技类的书打5星。

国人写历史的文学性在这本书里就表现出来了,即使写的科学史,即使写得还是最晦涩难懂的量子力学。个人感觉这本书写得比《时间简史》通俗多了,解决了我无数高中时只是背下来却不知道怎么回事的概念,诸如波粒二相性,测不准定则,薛定鄂方程等等看这本书的时候都明白了。窃以为由于测不准定则的存在,理论物理的研究其实早就到了极限了,现在相关的量子理论已经只是推理体系了,证明这些推理已经超出了人类能力的范围了,so 马克思主义里的什么人类是可以穷尽真理的现在看来是根本没有科学依据的。

这本书是按天下大势分久必合,合久必分来写的。你会看到诸如108将般的明星级科学家如何过五关斩六将将自己的理论推向极致又如何败走麦城晚年郁郁而终。量子力学就像一个泥潭,牛顿,波尔,薛定额,海森堡,爱因斯坦这些巨星们都没能保住晚节,在量子力学面前无法善终。这是一段科学家斗智斗勇的历史,这也是人类追求真理而不断奋斗的曲折史,战争激烈程度不亚于二战,将星云集不亚于三国,右边是一张网上流传的图片,你知道这是张量子力学大战后的合影么,你知道图下面是多么的暗流涌动么?


那些年,我们一起追的女孩

★★★★★

如果一本书你知道是不错的,但是当读的时候比你预想的还要好那么对这本书的好感就会直线上升,如果再和你的某种心理产生契合的话这种好感就会变得无法控制了。

和电影的重口小清新不同,小说是纯纯粹粹的小清新。与其说是小说倒更像是一篇篇随笔,记录一些小事,再写下些几行感慨。电影中很多经典的台词在小说中都要么有前言要么有后语,很多没有讲完的故事在小说中也都有了结尾,很多有些模糊的地方小说中也有很好的衔接,里面其实还有好几个感人的段子电影并没有体现,包括沈佳宜的一些戏份也忽略掉了。鉴于小说这种东西剧透是不道德的就不多写了,喜欢电影的同学可以去看看小说,会有意外的收获。


乔布斯的魔力演讲

★★★★☆

老罗说他演讲成功的秘密在于这本书,我看见书标题又笑了,你也要山寨乔帮主?

老罗第一年的演讲推荐里一堆课本把我给看伤了,好在第二年推荐的两本书都极其靠谱。话说演讲界的圣经应该是《演讲者之禅》这本书,但是我觉得这本书务虚的太厉害了,反而不如这本实用。而且在读得过程里你会慢慢发现,虽然乔帮主和老罗在做演讲的时候是两种完全不同的风格,但是在演讲技术层面上的东西是惊人的相似,包括幻灯片的布局,视觉元素的使用和视频的穿插,几乎都能给他俩做一个同步对照的视频了;还有讲故事的方式包括一些包袱的设置和对听众情绪调动的技巧也如出一辙。看着看着书我就脑子上冒出一条条黑线,原来当初我就是被你用这招给忽悠了。不过再想一下这两个人的演讲确实也有着充足的内容可以让他们发挥着各种演讲的技巧,他们又用着娴熟的技巧把听众忽悠的一愣楞的,果然都是内外功结合的大家。再想到雷军在小米发布会上的演讲……唉,too young too simple


好想回到小时候

★★★★☆

我的梦想是长大当个摊煎饼果子的。

其实排到第五就有很多候选者了,《暗时间》和《影响力》都不错,不过我想挑本有意思的,就在《历史是个儿什么玩意》和《郭德纲说北京》之间徘徊。后来一抬头发现这本书,这可是我这阵子买的唯一一本实体书呀。

话说我很喜欢这种草根风格的书,很生活有很有创意,有一些黑背的风格,不过是彩色的。书中将一些童年的趣事以漫画的形式表现了出来,勾起了许多美好有趣的回忆。不过也慢慢能发现一些值得思考的东西。为什么我们小时候的梦想都是什么科学家,军人,这些到底是谁的梦想呢?或许当时最真实的梦想就是当个摊煎饼果子的天天有薄脆吃吧。

Published: 30 Apr 2012

我也说一下写博客的原因

很多人都义正严词写了为什么要开自己的博客,貌似这是一件严肃的谨慎的事情,但我想说点不太一样的东西。

很正派的一种说法可以参照 《为什么你应该(从现在开始就)写博客》。看吧,写博客可以记录思维,可以寓学于教,可以交到朋友,甚至还能当简历哦亲。

可是我开博客的原因是——————我想找个地方随心所欲的吐槽啊有木有!找那么个地方可以仰天咆哮,满地打滚,四处挠墙啊有木有!不至于写了个“生活大爆炸”就因为有“爆炸”两个字瞬间被和谐了啊有木有!不用哪天突然想写个评论告诉你三天禁评啊有木有!然后还可一写篇吐槽文把一个人骂一顿,把他抓进来看看然后再自主和谐掉,他要找我麻烦我就说“你怎么证明我骂过你,你怎么证明,怎么证明?肯德基能证明么?麦当劳能证明么?金钱豹也不行呀。哦不,要是金钱豹的话我就帮你证明,我有存档哦亲,拿去随便看。”

evolution

看那些所谓的成名博客一个个写的都那么专业,他们难道就不知道博客博客,不广博怎么能叫博客。就该一秒技术宅,一秒变小清新,一秒天然萌,一秒变腹黑控,再一秒仰望星空心怀家国天下,接一秒变“你是我天边最美的云彩,让我用心把你留下来”。当然我要写成专业的你也管不着呀管不着。但我总觉得吧猿猴进化成人,不能再退化成现代科技包装的程序猿,天天罗列着机械的思维like left不然的话我会以为进化只是换身皮的,当然我也不是很相信进化论。

把博客起名叫部落一方面是谐blog音,另一方面是因为如果自称为博主的话感觉怪怪的,自称部落主我有种当山大王的感觉。然后就有种插根筷子就可以当族旗,打个草稿当图腾,搬个马扎当王座,生个病都可以考虑一下以后让谁继承部落大位这种关乎社稷命运的大事。至于oilbeater是我一开始欢乐的翻译打酱油的产物,之后被各种人曲解成什么汽油搅拌机,石油掠夺者之类不入流的东西,当然我原本的翻译也不是很高风亮节,但是我还是很喜欢这个欢乐的翻译。人活着嘛,就是要欢乐。

然后按照国际惯例发表感谢和吐槽:

  • 感谢iTesterBeiyuu 的代码支持,吐一下iTester你的代码是自动生成的吧,写得什么乱七八糟的。
  • 感谢jeklly提供的blog摸板和theme,吐一下你丫这么难看的theme也敢往网上放,居然还没更好看的了,过几天抽空种出一个theme来把你T了。
  • 感谢github提供的空间和支持,如果你能支持动态网页就更好了,当然我想多了……
  • 最后亲切问候GFW,不解释无条件的亲切问候。

Published: 28 Apr 2012