有趣的UNIX信号

 1、  前言1.1、     
为什么要写这个上周写的一篇文章关于僵尸进程的处理办法,几乎收到了寥寥无几的回复。
在和老左在上周的一个下午专门讨论关于僵尸进程的处理办法,并提了另外一个解决的途径和方案。在讨论的过程又更加深入的探讨了关于信号的方面的问题。老左建议我把当天的讨论的东西在这周在写一篇文章发送给大家。
1.2、     本文介绍什么      
这是一篇介绍UNIX下面关于信号对我们程序影响的文章。
       无论是开发还是测试都会遇上程序很运行效果预期不一致的情况。找到问题的根本原因和解决过程,就是排错。同时,如果问题发生的情况很特殊,很难以重现,在这样的情况下解决问题,非常具有挑战性。
       后面的章节通过例子来给大家说明一些问题。可能会有些杂乱无章。可能是当天爬了泰山后写的。脚痛得遭不住。但是一想到给大家分享问题,又不忍着痛把这篇文章给写了下来。

1.3、     本文使用的方法和说明      
在本文的的阅读过程中,可能会发现一些术语,或者知识点不熟悉。但是这些不能成为阻碍学习这些知识点的理由,我们鼓励自主学习。通过Internet都可以找到对应的知识点的具体说明。
       在内容的组织上尽量先给出现实的情况和问题,然后提供分析线索。最后是结论和思考。目的也是在于鼓励边阅读边思考。在拿到问题后,建议先分析如何去获取线索;拿到线索后自主分析和如何利用线索解决问题。
       我在处理这些问题的时候感觉很有趣,所以希望能给大家分享这种兴趣。这个是一个真正程序员真正要做的事情。在完成这篇文章的时候,并不是很严肃。所以大家也不要用严肃的眼光去看。希望是大家趣味阅读。

1.4、     您的反馈      
我努力去保证本文所提供的的内容的正确性。但是不能保证100%的正确。那么当阁下你在阅读的过程发现的问题和错误。都希望你能指出。您的反馈非常的重要。^_^

2、  正文内容
2.1、     热身运动
在进入正式的内容之前,我想首先给大家分享这么一个趣味问题。

镜子里面的图象,为什么左右是反的而上下不是反的?

我大学的时候物理老师问的一个问题,我也和爱好这些乱七八糟的朋友一起讨论过这个问题。但是最后大家都能难给出正确的答案。这里列举一下不同的一些观点。

1.  因为人的眼睛是左右排列的。
2.  如果把镜子横过来,左右就不反了,但是上下又反了。
3.  因为我们在南半球。

从技术层面上来说,这里涉及到的知识点只有镜子的反射,远比我们平时写UNIX C程序涉及的到知识点和问题要少一些。,但是要回答正确,却不是那么信手拈来。这个例子只是想说明,除了知识以外,解决问题还需要我们自己的勤奋的思考和清晰的解决思路。

2.2、     有点奇怪,但的确合理的发生了!      
我记得我写的第一个网络程序,是一个客服端程序。和ALINK进行联调程序的时候。时常都会出现收到的字符串收不全的。我的程序是这样的。
       lRet = read(sksock, szRecvBuff, 32);
例如服务器返回<K_RetCode>1</K_RetCode>这样一个字符串。但是偶然出现只能收到半截的情况。我当时很郁闷。
       我当时第一个想法是不是操作系统有问题。或者编译器有问题。我的代码明明没有问题。
       但是事实上是我错了。当我再次认真阅读了关于read函数的详细说明后。我不得接受read这个系统调用函数偶二会出现收不全网络字符的事实。
       在SCO UNIX 系统上 执行man S read 后我们在最后几页会发现这么一个说明
      
       In the following conditions, read and readv fail and set errno to:
       [EINTR]
          A signal was caught during the read or readv system call.

       在下面的条件 read和readv函数可能回失败并且它回设置一个全局的错误码为
       EINTR
              当正在执行read或者readv函数的一个信号被进程截取到。
       的时候会失败。

       OKAY,看到这里其实就已经很明确了。这个函数它是会失败的。而我处女作的网络程序完全是没有考虑这个问题。那么偶尔出现没收全字符串也是理所应该的。那么这个信号是什么时候产生的呢?说起这个问题我确实有点不好意思。由于当时老左喊我整个网络测试程序,我当时对这UNIX下面的网络程序确实从来没做过。就在胡乱找了的程序COPY了一段代码。不幸的是我多拷贝了alarm(60)这个行代码。这个就是生成信号的罪魁祸手。幸运的是我开始认识到这个信号的问题对我们的程序的影响。
      
2.3、     看似稀疏平常的东西,但是却很棘手      
在被信号的伤害了后,我决定开始学习一些信号的相关知识。我最开始以为信号就和WINDOWS操作系统上面的信息一样的异步通知的东西。但是这一次我又错了。伴随着对信号的深入认识。我才发现我最开始的想法是那么的幼稚。就正如这章的题目所示的一样。这个东西很是麻烦。
       接着上面的例子我们继续考虑问题。在上一个阶段,我们大家应该都有这样的一个认识了。read或者readv函数在执行的时候可能会被一个信号的发生打断。
       其实alarm(60)这个函数虽然他可能会打断我们的read函数,那么正是因为这一点。所以时常被大家用于控制read函数的超时控制。的确不错。
       但是事情并不是想象的那么简单。如果我们稍加思考的话,会继续想到另外的问题。如果read函数正在读的时候,发生了另外的一个信号的话,它是不是也会打断我们read操作呢?从man 中对read函数的出错的解释上面好象有这样的一种可能。
如果看过我上篇文章中对SIGCHLD信号的朋友们应该知道,这个信号是子进程退出的时候给父进程发送的一个报丧信号。我们永远无法保证我们的程序正在read的时候子进程不给父进程发送信号。细心一点朋友可能就会想到这个信号也要打断我们的read函数。
但是我在实际的测试中并没有发现SIGCHLD信号会中断read函数。我是这么测试的我写了一个客服端程序,然后连接服务器后,让客服端程序一直阻塞在read函数上面,然后这个时候 我给这个进程 kill –s CHLD pid (pid)是通过ps命令看到的客服端程序的进程号。 这个操作是什么意思呢?它就是给pid的进程发送一个SIGCHLD信号。信号是发送了。
但是read函数并没有因为这个信号的发生而中断。这个就是结论。
但是当我再执行kill –s ALRM pid的时候。不幸的是read函数就中断了。为了弄清楚这个事实,我最后找到了LIBC的原程序代码。认真的看了它的实现过程。在这个read函数里面 采用了sigemptyset、sigaction、sigaddset、sigprocmask
Sigdelset等原子操作函数处理 对SIGCHLD信号专门做了阻塞处理。那么这个信号是被阻塞了的。对于信号阻塞的概念,可以简单理解为这个时候信号是发生了,但是内核并没有把信号递交个目标进程。对于这个我不想在这里详细讨论。有兴趣的朋友可以阅读UNIX高级环境编程的第10章关于信号阻塞的描述。里面有个非常经典的sleep函数的实现解决方案。要想弄清楚信号阻塞我建议大家可以把UNIX下面的sleep函数的实现过程阅读一偏。你将会有质的变化的认识信号。
在本节中主要的目的不是给大家说明什么是阻塞信号,而是让大家认识到。并不是所有的信号都会中断read函数。

2.4、     刚开始我很绝望      
说实话,当问题发现到现在的程度后,我开始有点点灰心了。这个信号有的信号会中断read函数,有的又不会。一个新的问题又摆到我们的面前了。就是要找到那些信号会中断我们可怜的read函数。我想到了2个解决方案去弄清楚这个问题
1.  找到LIBC的原代码去看它的实现过程,编译弄清楚所有信号的处理完全过程。
2.  尝试UNIX下面的32种信号对read的影响。对每个信号都做一次 kill –s xxx pid,不过至少有一个信号你可以不用尝试那就是kill -9或者是kill –s KILL这个不仅仅是让read函数中断。连我们的程序都会直接被请出内存的。
我为什么会有点点灰心还有另外一个问题也摆在我面前。那么SOCKET应用中常用accept阻塞函数是不是也有同样的类似问题,它被中断的可能性又是那些信号呢?如果不把这些问题弄清楚,我们写的程序依然那么的不稳定那么不健壮。那么 7 * 24小时的稳定运行有点天方夜谈了。
       一切的猜想都还是没有事实说话来得重要。
上周中有个下午一直和老左讨论着这个问题。也为了弄清楚我们常用的平台是否都存在一样的情况。我做了一个专门的测试。在SCO UNIX ,HP UNIX操作系统下面所有的表示形式都一样。由于没有AIX的环境,所以没办法给出AIX的结果。
结果是这样的:
l         read函数的测试
对于SIGCHLD信号 无论如何 我在程序里面有没有说明它处理不处理 这个信号都不会中断read的处理
但是ALRM信号有点不幸.如果不设置这个信号的处理函数,那么程序还会直接被系统退出危险。
如果设置了这个信号的处理函数.情况稍好点.但是它会中断read 函数的处理.

l         accept函数的测试
对于SIGCHLD信号 无论如何 我在程序里面有没有说明它处理不处理 这个信号都不会中断accept的处理
但是对于ALRM信号。accept表现的还是依然的那么好。一直没没中断出来。

至于另外的一些SOCKET的操作函数的情况,在这里我没有继续列举出来,而是希望读者能去实践一下。并且能有测试的案例。把测试的结果汇集一下。发送到老左那里去,同时记得别忘记给我抄送一份测试结果的汇集资料。

2.5、     一个难以让人理解的事实      
增加这么一节我的本意只是为了给大家介绍一下 书上经常说的《不可靠信号》的意思。因为我觉得书上对这个描述的有点点让人误解。这个是自己在测试的过程中算是弄清楚原来书上说的 《不可靠信号》的一个方面的意思。

       不可靠在这里指的是,信号可能会被丢失——一个信号发生了。
       这个是原文中对信号丢失的一个说明。但是我总觉得它说得不是那么清楚。在下面的一个例子我想可能会有助于你去理解这句话。

我写了一个SOCKET SERVER 程序 主函数经过一系列的初始化后,阻塞在了accept函数我连续发送2次了ALRM信号和ALRM信号。由于我先前执行了signal(SIG_ALRM, alrm_func); 函数,并且alrm_func函数的过程是这样的
void alrm_func(int sig)
{
       prinf(“recv alrm signal”); fflush(stdout);
       Signal(SIG_ALRM,alrm_func);
}
按照正常的理解,这个时候我发送2次ARLM信号后。会在屏幕上连续打印出来2行
       recv alrm signal
       recv alrm signal
但是实际上缺什么事也发生。至少说明我发送的ALRM信号没有导致进程进入alrm_func处理函数。这个是为什么呢?
原因就是在于像上面说的 在accept函数中,对信号ALRM进行了阻塞处理。信号被内核保存着的。.
然后这个时候.我马上telnet过去,.本质上就是让accept函数立刻返回出来。.同时屏幕上打印出来了一行,注意是一行
       recv alrm signal 提示信息。
这个提示信息出来也说明信号ALRM被accept阻塞了。当accept结束的时候就被恢复了
       但是问题很奇怪为什么只有一行呢?
       这个就是 《不可靠信号》的一方面意思。UNIX系统的内核并不对信号进行队列的排队。那么我发送的2个信号,有一个就没有效果了。这个就是不可靠信号。

2.6、     我们还可以做得更好
再次声明一下,本文的从文章的角度出发写得有点杂乱无章。感觉好象什么都在说一样。缺乏一点条理性。姑且读者就按照这个是散文去看。也许觉得会稍微好些。但是我相信我们可以通过本文描述的一些知识点,加上自己的思考和操作可能会在写UNIX程序时候会更加多的去考虑一些问题。让我们共同为明天我们能写出更好的程序一起努力。
糟糕,我还在洗衣服,家里面的是半自动洗衣机,还要自己换水。真是麻烦。本文先这样告一个段落。下次再见。^_^

 

                                                                                                yl.tienon@gmail.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值