Posted on 2013-01-11 18:54
鑫龙 阅读(1447)
评论(1) 编辑 收藏 引用 所属分类:
本人分析研究
转载请注明 出自:http://www.cppblog.com/mysileng/archive/2013/01/11/197202.html
讨论两个由sigchld信号引起的血案问题,讨论的环境是服务端的并发程序。我们先把最原始的服务器端并发程序模型贴出来:
以上是服务器端程序,我们先不看被注视掉的部分,程序对于每一个accept的TCP连接会产生一个子进程,交给子进程去处理。而子进程其实并不做什么,直接睡眠3秒就结束。可以想象这样当子进程exit以后,会给父进程发sigchld信号,通知父进程自己挂了。但是在我们的父进程中,我们对于sigchld信号采用默认处置(忽略)。结果可想而知就是来一个连接,就产生一个僵死进程。我们运行程序3次,看看是否会得到3个僵死进程。
服务器端运行程序,被某客户端连接3次:
客户端运行程序执行3次,并查看进程情况:
首先声明,我们用客户端可以查看服务器端进程的原因是,我们把客户端和服务端放在了一台电脑上进程本次试验。我们并不关心cli客户端的具体实现,因为服务器并不从客户端获取任何信息。
从结果可知,果然服务端果断产生了3个僵死进程。接下来我们加上对sigchld的处理程序。但加上以后也将产生我们的第一个血案:
白色为客户端,黑为服务端:
可见,僵死进程的问题已经解决,但还有个潜在的隐藏危机。
PHOSIX对于向accept这种慢速的系统系统调用有一个基本规则(apue,unp都有涉及):当进程阻塞于某个慢系统调用的时候(我们的程序是accept),当进程捕捉到某个信号(我们的程序是sigchld),并从信号的处理函数返回时(我的程序是deal函数),进程不再阻塞与之前的慢速系统调用,而是返回一个EINTER错误。
对于上面的这个规则,各个操作系统的对待方式是不同的。有的操作系统返回EINTER以后,就会自动重启之前的慢速系统调用而继续,有些则不会自动重启。对我们实验程序的这个操作系统环境(centos5.5),从结果来看,因为并没打印"accept error"并退出程序,我猜想,centos5.5应该是会自动重启慢速系统调用的。也就是说在这里我因为操作系统的优秀,躲过一劫(躲过第一次血案)。但为了可移植性我们应该改进程序为以下实现:
我的改动主要集中在对accpt的错误处理里面。接下来阐述另外一个血案
----------------------------------------------------------------------------
我们继续沿用上述的最后一次服务端程序来进行接下来的实验,现在我们编写一个客户端程序,客户端一次性跟服务端申请5个连接。客户端的程序如下:
整个程序的构架大概如下:
这里客户端最需要注意的是程序的最后一句并不是一个个close所有的套接字描述符,而是调用exit程序结束进程。根据APUE描述,exit系统调用会执行关闭该进程所有描述符的操作,也就是说客户端的所有描述符,包括套接字描述符也被几乎同时关闭了。也就是说服务端的由监听进程产生的所有处理子进程也会在几乎同时死掉。那么就会在几乎同时给父进程发送sigchld信号。情况如下:
血案即将发生。请注意,根据APUE对于信号在1-31之内的的信号,因为历史原因,是不可靠信号,也就说,SIGCHLD信号在被递送到正在阻塞SIGCHLD信号的进程时,是不会排队的,而是会被系统压缩。上述问题就是当5个sigchld信号几乎同时到达父进程时,只有第一个能顺利被父进程的信号处理函数处理。又因为被signal/sigaction设置的信号处理函数会自动阻塞正在处理的信号这一原则,接下来没被处理的4个sigchld信号,被排在了父进程门口。不巧的是,sigchld又是不可靠信号,结果是4个sigchld被压缩成一个sigchld信号。这就导致信号的丢失。也因为丢失了3个sigchld信号,就会产生3个僵尸进程。你说这是不是一个名符其实的血案。接下来我们实验一下:
可以清晰的看到结果如预期,所有出现信号丢失导致3个僵死进程。那么怎么解决这个问题呢?~。。。。