月度归档:2014年02月

2013年的环台湾(13): 宜兰-基隆-淡水-台北

这一天的路线较长,从宜兰沿着海岸线,经过基隆,新北,淡水,最后回到台北。

基隆港

在路上碰到了几个台湾的骑行者,其中一个叫阿忆,桃园人,今天也是他环岛的最后一天,晚上要骑回家,路程很长。他在上海工作,跟我聊起基隆时说他的大学是在那里读的,现在基隆已经大不如以前了,人才都被台北吸走了。

我们骑了一段,后来节奏不同在基隆走散了。陆续碰到其他的几个骑行者也目标不同没有一块骑。在绕过台湾岛最北边的拐角(新北市的石门区)之前,天已经黑了。过了那个拐角,风向变成顺风了。往淡水的路上骑的很快,因为离最终的目的地越来越近了,期待快些结束。

到达淡水的时候下起暴雨(受台风影响),浑身浇透,在淡水避了一会儿雨,等雨小些了才穿上雨披继续骑。对于淡水这个城市是很多年前在家里凤凰卫视音乐台放过《流浪到淡水》这首歌的MTV,才知道这个地方的,那大概是1997年的时候。

从淡水回台北的路上,发现车胎扁了。这时已经晚上11点多,我沿路找了一个有灯光的地方,那儿正好是一家“铃木牌”汽车销售店。我把车子推到棚子下面,不想因为离那些要销售的汽车有些近,触动了他的警铃。我没太在意,过了一会儿警铃就消失了,我坐在地方开始换车胎。这是第一次换车胎,尽管有工具,还是琢磨了一会儿,动作很慢。

在车胎还没有拿下来的时候,突然来了两辆警车,下来了6、7个警察,查询我的证件。我这才意识到问题,原来汽车店的老板在收到警铃时报了警,经过一番解释才了解我是为了找个避雨且有灯光的地方好换车胎。年长的一位警察对我说很佩服你来环岛的勇气,但你闯入了私人领地。在征求了汽车店老板的同意后,两个年轻的警察留下来等着我在那里把车胎换好。

我没弄清楚这家汽车店老板是哪儿人,似乎他与警察沟通的时候说的不是闽南话,更像是日语。我在向他道歉后,他是用非常生硬的国语说“没关系”。两个年轻的警察陪我换车胎的时候,一个还帮了我一把。另一个问我环岛的时候有没有经过“屏东”,我当时一下子没有记起来屏东具体在哪个地方,猜测大概在台南一带,跟他说我经过过但没有停留。我想屏东大概是他的老家吧。

换好了车胎之后已经快12点,而这时我还没有进入台北市区(大概是在关渡或北投那一块儿),继续骑车到市区中心时已经凌晨1点多,仍旧是第一天到台北时住的那家旅馆。

2013年的环台湾(12): 名不虚传的苏花公路

体验了逆风的艰难之后,对从花莲到宜兰更加谨慎了一点,因为这一段要经过几十公里的苏花公路。

苏花公路绝对是这次骑行的高潮。这段路完全在海边的悬崖上,并且这条公路是沿着山壁,蜿蜒曲折,不断上坡下坡,我遇到最危险的情况是在某个山顶的时候,遇到大概7,8级的大风(把沙子也刮起来打在我脸上),当时骑着车已经有些摇晃,只好推着自行车;看到路边的指示牌上写着“注意落石”,心里有些紧张,想着一定要在日落前能够走完这几十公里的山路。

2013年的环台湾(11): 成功-花莲

早晨在小镇上吃过早饭后出发,这一天的目标是到达花莲。绕到东线之后,风向变了;骑起来受力很多。上路后不久,遇到一位老先生,从后边赶超我,他的自行车上放了一个外置音箱,喇叭声音很大。

我们一同骑了几十公里,了解到他是从台东出发的,也就是说他碰到已经骑了好几十公里,他的讲话有些口音听的有些吃力。大致了解到他已经退休,以前是从事养殖海产品的。退休后他有了大把的时间,骑车来锻炼身体。告诉我这边的一些骑行经验,在警察局可以打气、加水等;也教会了我怎么看轮胎上印的气压限制,这样可以在警察局用他们的气筒时(上面有个压力指盘)知道打气到什么程度。那天的风有些大,路上骑起来很吃力,老先生到时比我要更从容一些。

在中午前,老先生原路返回;我继续向前,东线的风景比西线要漂亮的多,左边是凸起的山脉,右边是湛蓝的太平洋,当然因为季风骑车时付出的代价也要更高。

台东线,经过北回归线

2013年的环台湾(10): 兰屿-台东-成功

在兰屿住了一晚,第二天吃过早饭坐船返回时,遇到了前几天在鹿港碰到的两个福州的家伙,他们比我晚了一天

当时从兰屿回台湾本岛的船只去台东,这也意味着我缺少了一段从恒春到台东的路程。略有遗憾。

回程的船上继续吐,不过这次因为吃过早饭比上次好受一些。到达台东的时候已经中午。我推着车出码头,因为吐了两次之后,身体状态受到很大影响,没有确定当天的目的地,看骑到哪儿是哪。

那天晚上我是在一个叫“成功”的小镇上休息的,小镇上人不多,有些小杂货铺子里居然仍在买一些磁带,我在其他地方也看到过还有租赁DVD的,这些在大陆已经看不到。

2013年的环台湾(9): 达悟族

住的那家民宿还提供了一些游玩项目,我只体验了一下当地的“拼板舟”,带我划船的,是一位岛上的原住民,他们属于“达悟族”

我之前一直没弄明白大陆统计的五十六个民族里,是否有完整的包含台湾的各个少数民族,还是以“高山族”把台湾的少数民族都代表了?至少我没有在五十六个民族里找到达悟族。他们看上去与汉人的特质特征已明显不同,个子也较矮一些。

兰屿整个岛还是很“原生态”的,完全没有像大陆或台湾其他旅游胜地的那种喧嚣,但在我回来后搜索有关兰屿的历史时吃惊的发现这个岛居然被国民政府埋藏过核废料。在flickr上发现的这张照片,因为未经过作者同意,这里只给出链接:http://www.flickr.com/photos/justphilos/9722455407/in/photostream/,这个地方我骑车经过过,完全没有料到是一个核废料的存储点。

2013年的环台湾(8): 兰屿

第二天一早,我赶去后壁湖坐7点钟的船去往兰屿。昨晚从旅馆老板那儿得知这是一条大船,看到之后才发现是条小船,船舱只有几十人的座位。至少与大陆的游轮比起来只能算是一条小船了。

船刚驶出码头的时候还很兴奋,拿着相机出船舱拍照,过了一会儿发现颠簸的厉害,回到船舱,再过一二十分钟,开始感觉头晕,想要呕吐,船舱里有几个人也已经开始吐了。我试图强忍着,让自己昏睡;但不奏效,早上没有来得及吃东西,胆汁都吐出来了。

登岛的时候有些小雨,雾很大,第一眼看到岛上的山被雾气遮挡,非常诧异这么小的岛上山却有这么高。后来我了解到台湾的最高峰有3900多米,不像大陆是东往西海拔逐渐增加,黄山、华山的高度也才不过两千米左右,相比起来台湾的山算是比较突兀的了。

我在岛上停留了一天,住在一家民宿。把这个小岛环绕了一圈,景色确实非常棒。我去的时候人不太多,每年的10.5号之后会因为风浪较大而封船,只有飞机可以去。

2013年的环台湾(7): 恒春

没在高雄怎么停留,在市区只是稍微转了一下,吃过早饭后9点多出发,这一天的目的地是到台湾岛的最南端恒春,然后第二天从那儿坐船去兰屿。

这一天很少拍照,一路很多的风景我现在都没有印象了,路程并不太远,所以骑得比较轻松。在海边沿线,有很多流动的咖啡车,找个地方搭几个凉棚,在车里有机器做咖啡,卖的也不贵。

我到达恒春的时候太阳还没落山,先赶往后壁湖码头,问好第二天去往兰屿的船是几点钟出发。然后在码头附近转悠了一下,太阳落山后开始寻找旅馆,但运气不太好,附近的旅馆已经住满了。通过google map查到不远处有几家民宿,骑车去过看看运气。我先是看到一家叫“卡米特”的民宿,骑车过去的路上没有人,也几乎没有房子和灯光。非常安静,只听到一阵阵海潮的声音。

骑车爬过半个山坡,看到一块有路灯的指示牌写着那家民宿的方向,望去那是一座不小的”庄园“,进去后也没有人,一楼的客厅里放着很舒缓的轻音乐。我喊了一下没有人响应,一只大黑狗跑出来冲着我叫,我用自行车挡在前边,它没有扑过来;几分钟后一个男的下来,叫这只狗black,说它并不会咬人,只是看到我车上面的电筒在闪,它会害怕;问我有没有预约,我说没有,他回去查看了一下,说房间都已经满了。

我骑车原路返回,当时有些疲惫,晚饭还没吃。这个庄园式的民宿,让我突然想起《现代启示录》里那几个美国士兵在脏臭的湄公河沿岸,大雾散后突然遇到了一群法国人以及他们在那里的庄园。

我在另一个地方找旅馆和民宿时也已经满了,路上看到一位在房子边上抽烟的人,问他附近是否还有住宿的地方,他骑着摩托带我去了一个家庭旅馆。我看了一下环境也还不错,老板很热情,问我有没有吃过,我说没有,他们说就家里的饭菜吧,我说给他们钱,他们说吃饭不用的。在他家的餐厅,热了几个菜,煮了一碗面给我,除了他们腌制的一种鱼,还有一个菜看上去很像藻类,但他们告诉我说这是当地的一种菇,他们叫“雨来菇”,是下雨天才能找到的。

吃过饭后,与老板以及他们两儿子在客厅看电视喝茶,告诉我说如果不是大陆游客来这边,生意会很冷清,对大陆开放了自由行后让他们很受益。他的大儿子与我年级相同,说起前不久去过长沙,感觉变化很大。虽然是在南部,他们一家人倒是支持蓝营的,反对台独,或许与他们父辈是从福建来的有关。

2013年的环台湾(6): 在高雄遇到的两位前辈

两位前辈前辈都已55岁,聊起来他们曾在1974年和1980年的时候骑单车环过两次岛。

这张照片是后来email的,是他们在1980年环岛时于枋寮车站照的,照片从左第一位和第三位是这次路上遇到的。

从台南到高雄的这端途中,跟随两位前辈同行,其中一位偏外向,一位偏内向。路上大多时间在和这位外向的前辈聊天,聊了很多有关宗教、政治、旅行、互联网等方面的话题。在途中,他刻意选择一个要经过的教堂去参观了一下。

在台湾的一路骑行中,发现即使再小的村庄,也会有菩萨、妈祖的庙,异或是基督、天主教堂,甚至清真寺,信仰的印记在这里随处可见。

到达高雄的时候,天色已黑,前辈请我去了一家,不在闹市区,普通人还有点难找的“酸菜火锅”饭馆。我非常奇怪,台湾南部怎么会有这种北方家常菜,后来也是从龙应台的《大江大海1949》这本书里了解到当年有相当多的山东籍国军从青岛乘船到高雄。这家饭馆所在的地方,他们称为“眷村”,也就是军队家属们所住的地方。

我们要了一份火锅,饺子,以及几个菜。酸菜和饺子口味很地道,并没有”本地化”,不知道饺子用的面是不是从大陆运过来的。

两位前辈虽然年级较大,但对新事物的接受能力很强,我们也聊了彼此的工作,对于互联网,他们是facebook的重度用户,也了解大陆大多人在用微信、微博等产品。看到我用的小米手机,他们说小米手机在台湾也非常受欢迎。我跟他们提到内地一些互联网产品,以及几家大公司的情况。也从他们那儿了解了一点台湾高铁的建造过程。

那天晚上到10点左右,他们送我到市区中心旅馆较多的地方。我们交换了email,后来我回来后把我们在教堂的合影发给了他,他也发给我他们年轻时的这张照片。

回顾2013

1) blog

去年4月份开始购买了阿里云的vps作为blog服务器,之前曾经过用一些其他提供商的vps都不太稳定,阿里云的vps体验下来还是非常稳定的,十个月的时间里从未宕机,mysql服务器也只在最初因为交换分区设置不当有过问题,解决后也一直很稳定。只是国内的服务器需要网站备案,还好万网在公司园区就有办事处,还算方便,提交了相关材料后过来大约两周就好了。从5月份开始写一些scala相关的东西,累积下来现在也有一百多篇blog了(当然也有一些内容是以前就写好的)。

当时是觉得需要一个说话的地方,在内部scala还是个小众群体,我需要一个地方把scala和jvm方面的经验沉积一下,同时也能让这个圈子的人注意到,有更多的交流和反馈。现在看目标也算达到了,去年写的170多篇blog里有120篇左右是与scala相关的。有70多条评论,也基本是围绕scala的。

写blog是个很耗精力的事情,但好处也很明显,除了提高表达能力,文章中的逻辑错误和问题也会得到别人的指出和补充。会让自己思考的更缜密一些。我不知道今年会不会像去年写那么多,但这个blog会尽力经营下去。以前也曾搞过独立博客,但因为vps的稳定性或其他问题没有持续下去。

在写blog的过程中,为了让自己能保持有写作的欲望(或者说减轻写作过程中的压力),尽可能把篇幅控制的精短一些;需要长篇大论的东西都分成多篇来写,这个方法对我很有效。

2) scala:

对scala关注了大约有3年左右的时间了,去年想要翻译或写一本scala方面的书,想要翻译的那本书很遗憾认识的出版社最终没有拿到版权,而写书感觉现在的积累还不够,尤其是实战方面。“类型系统”这部分内容是可以总结一下了,有考虑把类型系统组织成一本电子书,在豆瓣上免费发布,不过这块儿还有些收尾的东西,写起来并不容易。

去年和scala的圈子交流的还不错,在内部组织了几次分享,也和杭州地区用scala的公司比如与19楼交流了一下。五月份去上海参加了一下scala爱好者聚会,七月份在阿里技术嘉年华上做了一个分享,十月份和同事在杭州组织了一次华东地区scala交流

年底的时候尝试用spray-routing做一些事情,体会到其核心设计是一个monad,又试图写一系列blog来解释monad模式。但这个坑挖的有些大,想很细致的说明这个模式还需要了解很多东西,看了一些haskell方面的资料;目前写了有7篇,感觉还有些“虚”,后续想用一些例子来说明,想要把一些常见的monad实现也用来举例,但想的有些多,比如continuations怎么用monad实现,蹦床monad等,但发现又要先说明continuations、trampoline的作用和意义这过程中又扯出一堆新的问题,结果这个系列一直还没有弄完。还去找了很多用continuation来实现一些有价值的功能(比如coroutine特性),但发现scala的continuations如某些人说的:powerful, but useless.

3) 旅行:

2013年去了几个之前没有去过的地方,7月份随团队活动,去了青岛。国庆假期期间去了一趟台湾,骑自行车9天环岛一圈,一些见闻见这个系列,还没写完:(,照片在这里:http://www.flickr.com/photos/whj/sets/

4) 跑步:

参加了3次半程马拉松,2月份的重庆马拉松,4月份的扬州马拉松,9月份的贵州黄果树马拉松。这一年跑步方面是断断续续,也与从滨江搬到城西有关。

5) 电影:

从豆瓣的记录来看,去年大约看了五十多部电影,平均每周一部。但印象较深的电影现在却说不上来。不像2012年看过的《普罗米修斯》和《焦土之城》这种让人为之一振的电影。当时《普罗米修斯》让我重新对科幻类型的电影产生了兴趣,原来我一直误解了之前没看过的“异形”系列,它的原名是《Alien》即外星人,是讨论人类起源这个宏大的主题的。

6) 阅读:

这一年看的书绝大多数仍是技术方面的,一小部分有关旅行、历史、文学、自我管理等方面的。春节假期的时候读了《当我谈跑步时,我谈些什么》,和作者在跑步时有很多感受相似,是一本有部分共鸣的书。技术方面松本行弘的《代码的未来》是一本不错的书,这本书本身不怎么涉及技术细节,但如果你想要开发一门语言的话,这本书能带来很多思考。另外还有一本书非常想推荐的是:《人体:人体结构、功能与疾病图解》

shell脚本里后台进程忽略SIGINT信号问题排查

这篇blog是同事涧泉在和我一起分析tomcat进程意外退出时,针对`SIGINT`的疑惑,他的一些记录,我这里转载一下。

问题简述

写一个简单的 C 语言程序 “a.c”, 用一个死循环 hold 住.

int main() {
    for (;;) { }
    return 0;
}

写一个简单的 makefile 文件. 为了方便调试, 添加 -g 参数生成调试信息.

a: a.c
    gcc -g a.c -o a

编译程序:

[observer.hany@v125205215 ~/tmp]$ make
gcc -g a.c -o a

在当前 shell 下后台运行程序, 使用 kill 命令向进程发 SIGINT 信号, 可看到进程正常中断退出.

[observer.hany@v125205215 ~/tmp]$ ./a & 
[1] 32533
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD
54241    32533  9744 98 11:37 pts/1    00:00:05 ./a

[observer.hany@v125205215 ~/tmp]$ kill -SIGINT 32533
[observer.hany@v125205215 ~/tmp]$ jobs
[1]+  中断                    ./a
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD

编写如下简单 sh 脚本 “a.sh”.

#!/bin/bash
./a &

使用脚本启动后台进程, 再使用 kill 向进程发送 SIGINT 信号, 进程却不会退出.

[observer.hany@v125205215 ~/tmp]$ ./a.sh 
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD
54241    12448     1 81 11:50 pts/1    00:00:03 ./a

[observer.hany@v125205215 ~/tmp]$ kill -2 12448
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD
54241    12448     1 98 11:50 pts/1    00:37:24 ./a

但直接 kill (默认发送 SIGTERM 信号) 可以使进程退出.

[observer.hany@v125205215 ~/tmp]$ kill 12448
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD

确认进程是否收到信号

尝试用 strace 命令查看两种情况下程序运行的差异.

第一种情况, shell 下直接运行程序:

[observer.hany@v125205215 ~/tmp]$ ./a &
[1] 5309
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD
54241     5309  9744 99 12:51 pts/1    00:00:03 ./a

使用 strace 监控进程:

[observer.hany@v125205215 ~]$ sudo strace -p 5309
Process 5309 attached - interrupt to quit

发送 SIGINT 信号, 可以看到进程退出了.

[observer.hany@v125205215 ~/tmp]$ kill -SIGINT 5309
[observer.hany@v125205215 ~/tmp]$ jobs
[1]+  中断                    ./a
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD

strace 监控到完整输出如下:

[observer.hany@v125205215 ~]$ sudo strace -p 5309
Process 5309 attached - interrupt to quit
--- SIGINT (Interrupt) @ 0 (0) ---
Process 5309 detached

第二种情况, 使用脚本在后台运行程序:

[observer.hany@v125205215 ~/tmp]$ ./a.sh 
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD
54241     9512     1 99 12:55 pts/1    00:00:03 ./a

使用 strace 监控进程:

[observer.hany@v125205215 ~]$ sudo strace -p 9512
Process 9512 attached - interrupt to quit

发送 SIGINT 信号, 进程没有退出.

[observer.hany@v125205215 ~/tmp]$ kill -SIGINT 9512
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD
54241     9512     1 98 12:55 pts/1    00:02:07 ./a

strace 可以看到进程确实收到了 SIGINT 信号, 但进程没有退出.

[observer.hany@v125205215 ~]$ sudo strace -p 9512
Process 9512 attached - interrupt to quit
--- SIGINT (Interrupt) @ 0 (0) ---

直接 kill (默认发送 SIGTERM 信号), 进程才退出.

[observer.hany@v125205215 ~/tmp]$ kill 9512
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD

完整 strace 输出如下:

[observer.hany@v125205215 ~]$ sudo strace -p 9512
Process 9512 attached - interrupt to quit
--- SIGINT (Interrupt) @ 0 (0) ---
--- SIGTERM (Terminated) @ 0 (0) ---
Process 9512 detached

确认进程信号处理逻辑

查了一下 gnu libc Termination-Signals 文档和 linux man 7 signal 文档,
默认情况下进程对 SIGINT 和 SIGTERM 信号的处理过程都是 Term, 终止进程.

strace 只能看到进程收到了信号, 看不到进程对信号的处理过程.
猜想是不是可能两者的处理逻辑并不完全一致,
SIGINT 做了什么特殊判断, 在某种特殊情况下不终止程序.

尝试用 systemtap 监控 probe::signal.handle, 看是否能找到两种信号处理程序的差异.
因为测试机上 linux 内核和 stap 版本太低, 无法监控 “signal.handle”.
要调试具体信号处理逻辑, 需要调试到 linux 信号处理的内核代码,
目前难以做到, 放弃了这种方式.

查看进程信号处理 handler

因为不用 shell 启动进程时, 如 ubuntu 下 “Alt + F2” 运行程序,
进程也是会被 SIGINT 正常终止的.
怀疑是 shell 修改了进程的默认信号处理器.
尝试用 systemtap 监控 “syscall.sigaction” 系统调用,
测试机上的 linux 内核和 stap 版本也不支持监控 “syscall.sigaction”.
尝试监控了一下 probe::signal.do_action, 输出信息太多, 没有找到有用信息.

修改程序代码 “a.c”, 让进程启动时自己查询对应的信号处理器, 重新编译程序.

#include <stdio.h>
#include <signal.h>

struct sigaction hdl_int;
struct sigaction hdl_term;

int main() {
    sigaction(SIGINT, NULL, &hdl_int);
    sigaction(SIGTERM, NULL, &hdl_term);
    for (;;) { }
    return 0;
}

第一种情况, 直接在 shell 启动程序:

[observer.hany@v125205215 ~/tmp]$ ./a &
[1] 29385
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD
54241    29385  9744 99 13:50 pts/1    00:00:04 ./a

使用 gdb attach 到进程 (省略了一些冗余输出):

[observer.hany@v125205215 ~/tmp]$ gdb a 29385
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-37.el5)
... ...
Loaded symbols for /lib64/ld-linux-x86-64.so.2
main () at a.c:10
10      for (;;) { }

可以看到进程中断到第 10 行死循环的位置, 使用 gdb 查看获取的两个信号处理器信息:

(gdb) p hdl_int
$1 = {__sigaction_handler = {sa_handler = 0, sa_sigaction = 0}, sa_mask = {__val = {0, 
      0, 140526787057128, 1, 0, 1, 210456656896, 140733374433184, 0, 210455544752, 
      140526787062592, 4476012448, 4294967295, 140526787064672, 6293608, 0}}, 
  sa_flags = 0, sa_restorer = 0x31002302d0 <__restore_rt>}
(gdb) p hdl_term
$2 = {__sigaction_handler = {sa_handler = 0, sa_sigaction = 0}, sa_mask = {__val = {0, 
      0, 140526787057128, 1, 0, 1, 210456656896, 140733374433184, 0, 210455544752, 
      140526787062592, 4476012448, 4294967295, 140526787064672, 6293608, 0}}, 
  sa_flags = 0, sa_restorer = 0x31002302d0 <__restore_rt>}

看到两个信号处理器都是 “0”, 从信号处理头文件中可找到如下常量定义:

/* Fake signal functions.  */
#define SIG_ERR ((__sighandler_t) -1)           /* Error return.  */
#define SIG_DFL ((__sighandler_t) 0)            /* Default action.  */
#define SIG_IGN ((__sighandler_t) 1)            /* Ignore signal.  */

可知 “0” 表示默认信号处理程序, 但实际的信号处理逻辑函数地址是看不到的.

向进程发送 SIGINT 信号, 进程终止:

(gdb) signal SIGINT
Continuing with signal SIGINT.

Program terminated with signal SIGINT, Interrupt.
The program no longer exists.

第二中情况, 使用脚本启动后台进程.

[observer.hany@v125205215 ~/tmp]$ ./a.sh 
[observer.hany@v125205215 ~/tmp]$ ps -C a -f
UID        PID  PPID  C STIME TTY          TIME CMD
54241    13670     1 99 14:08 pts/0    00:00:03 ./a

gdb 重新 attach 到新进程:

(gdb) attach 13670
Attaching to program: /home/observer.hany/tmp/a, process 13670
Reading symbols from /usr/local/snoopy/lib/snoopy.so...done.
Loaded symbols for /usr/local/snoopy/lib/snoopy.so
Reading symbols from /lib64/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib64/libc.so.6
Reading symbols from /lib64/libdl.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/libdl.so.2
Reading symbols from /lib64/ld-linux-x86-64.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib64/ld-linux-x86-64.so.2
main () at a.c:10
10      for (;;) { }

查看信号处理器信息:

(gdb) p hdl_int
$3 = {__sigaction_handler = {sa_handler = 0x1, sa_sigaction = 0x1}, sa_mask = {__val = {
      0, 0, 140457683965416, 1, 0, 1, 210456656896, 140733569554304, 0, 210455544752, 
      140457683970880, 4671133568, 4294967295, 140457683972960, 6293608, 0}}, 
  sa_flags = 0, sa_restorer = 0x31002302d0 <__restore_rt>}
(gdb) p hdl_term
$4 = {__sigaction_handler = {sa_handler = 0, sa_sigaction = 0}, sa_mask = {__val = {0, 
      0, 140457683965416, 1, 0, 1, 210456656896, 140733569554304, 0, 210455544752, 
      140457683970880, 4671133568, 4294967295, 140457683972960, 6293608, 0}}, 
  sa_flags = 0, sa_restorer = 0x31002302d0 <__restore_rt>}

这时看到 SIGINT 的信号处理器 hdl_int 是常量 “1”,
从头文件常量定义可查到即是 SIG_IGN, 忽略信号.

问题终于水落石出了, 原来是第二种情况, 在 shell 脚本运行后台程序时,
SIGINT 的信号处理器被设置成了 SIG_IGN, 忽略信号.

发送 SIGINT 信号, 可看到程序继续运行不会退出,
发送 SIGTERM 信号后, 程序终止.

(gdb) signal SIGINT
Continuing with signal SIGINT.
^C
Program received signal SIGINT, Interrupt.
main () at a.c:10
10      for (;;) { }
(gdb) signal SIGTERM
Continuing with signal SIGTERM.

Program terminated with signal SIGTERM, Terminated.
The program no longer exists.

确认是进程信号处理器被修改,
重新修改监控 probe::signal.do_action 的 stap 脚本,
缩小监控范围, 只监控信号为 SIGINT (常量 “2”) 并且用户 pid 为当前测试用户的调用.

function time_str() {
    return ctime(gettimeofday_s() + 8 * 60 * 60);
}

probe begin {
    printdln(" ", time_str(), "BEGIN");
}

probe end {
    printdln(" ", time_str(), "END");
}

probe signal.do_action {
    if (sig != 2
        || uid() != 54241
        // || execname() != "a"
        // || execname() != "bash"
        ) {
        next;
    }
    printdln(" ", time_str(), execname(), name,
        sig_name, sa_handler,
        "")
}

启动 stap 监控脚本:

[observer.hany@v125205215 ~]$ sudo stap -g trace-sig.stp
[sudo] password for observer.hany: 
Tue Feb 11 14:26:43 2014 BEGIN

再次执行 ./a.sh 脚本, 果然监控到如下输出:

Tue Feb 11 14:29:50 2014 bash do_action SIGINT 4491008 
Tue Feb 11 14:29:50 2014 bash do_action SIGINT 4491008 
Tue Feb 11 14:29:50 2014 bash do_action SIGINT 0 
Tue Feb 11 14:29:50 2014 a.sh do_action SIGINT 0 
Tue Feb 11 14:29:50 2014 a.sh do_action SIGINT 0 
Tue Feb 11 14:29:50 2014 a.sh do_action SIGINT 0 
Tue Feb 11 14:29:50 2014 a.sh do_action SIGINT 1 
Tue Feb 11 14:29:50 2014 bash do_action SIGINT 4491008 
Tue Feb 11 14:29:50 2014 bash do_action SIGINT 4491008 
Tue Feb 11 14:29:50 2014 bash do_action SIGINT 4703312 
Tue Feb 11 14:29:50 2014 a do_action SIGINT 0 

确认是 “a.sh” 脚本进程执行了将 SIGINT 的处理器设置成 “1”, 即 SIG_IGN, 的操作.

非交互式 shell 问题

后来找到 Get rid of SIGINT 这篇文章 (需要翻墙才能访问),
讲到这是非交互式 shell 的一个问题.

shell 命令行下是交互式模式 (interactive),
运行是脚本时是非交互式模式 (non-interactive).
非交互式 shell 默认禁用了 job control,
这时启动后台进程时 shell 会设置后台进程忽略 SIGINT 等信号.
大概行为伪码如下:

if(!fork()) {
  /* child */
  signal(SIGINT, SIG_IGN);
  signal(SIGQUIT, SIG_IGN);

  execve(...cmd...);
}

execve 启动进程时会继承信号处理器:

  • POSIX.1-2001 specifies that the dispositions of any signals that are
    ignored or set to the default are left unchanged.

因此默认情况下 shell 脚本启动的后台进程会忽略 SIGINT 等信号.
可以在 shell 脚本中设置 set -m 打开 job control, 避免这个问题.