﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-mysileng-随笔分类-本人分析研究</title><link>http://www.cppblog.com/mysileng/category/20298.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 15 Jan 2013 12:48:07 GMT</lastBuildDate><pubDate>Tue, 15 Jan 2013 12:48:07 GMT</pubDate><ttl>60</ttl><item><title>select函数与stdio混用的不良后果 (原)</title><link>http://www.cppblog.com/mysileng/archive/2013/01/15/197284.html</link><dc:creator>鑫龙</dc:creator><author>鑫龙</author><pubDate>Tue, 15 Jan 2013 05:09:00 GMT</pubDate><guid>http://www.cppblog.com/mysileng/archive/2013/01/15/197284.html</guid><wfw:comment>http://www.cppblog.com/mysileng/comments/197284.html</wfw:comment><comments>http://www.cppblog.com/mysileng/archive/2013/01/15/197284.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mysileng/comments/commentRss/197284.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mysileng/services/trackbacks/197284.html</trackback:ping><description><![CDATA[&nbsp; &nbsp; &nbsp;转载请注明 出自：http://www.cppblog.com/mysileng/archive/2013/01/15/197284.html<br /><br />&nbsp; &nbsp; &nbsp;今天在看UNP6.5节，学习到了select与stdio混用的后果。特此进程实验一番。再实验之前需明确一下几点：<br />1.stdio流的i/o函数 与 系统i/o函数不同。stdio流函数在用户空间和内核都有缓冲，系统i/o函数只在内核有缓冲，用户空间没有。<br /><br />2.stdio流的i/o函数缓冲机制：在面对文件时候用的是全缓冲，面对设备的时候用的行缓冲。(等下试验用的是键盘和屏幕)，所以实验用的stdio函数采用行行缓冲。<br /><br />3.select函数对于某一个描述符是否准备好可读可写，是对内核缓冲区中的数据是否达到某一个最低标准，而不是用户缓冲区。也就是说select函数不知道用户缓冲区的存在。<br /><br />首先写了一个系统i/o函数 简单的select函数程序：<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130115122357.jpg" width="527" height="387" alt="" /><br />&nbsp; &nbsp; &nbsp;程序给select函数只设置的键盘的描述符。也就是说如果键盘的描述符准备好了就不再阻塞。但是这里有一个问题，解除阻塞后，我们最多只从内核缓冲区读3个字节，这个时候就会有两个情况：<br />（1）内核空间本来存储的数据就小于等于3个字节，全被读走。那下次再次调用select函数，应该肯定会阻塞的，因为键盘输入的内核缓冲区已经没有数据了。<br />情况如下(内核空间只有3个字节：1 2 \n):<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130115122952.jpg" width="331" height="100" alt="" /><br /><br />（2）如果内核空间的数据多余3个字节，但是因为最多只能读3个字节，就必将导致内核中有数据读不完。那么下次再遇到select函数的时候是否会阻塞呢？<br />情况见下:<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130115123255.jpg" width="296" height="72" alt="" /><br />&nbsp; &nbsp; &nbsp;当我们输入5个字符时候(1 2 3 4 \n)，第一次read掉3个字符，内核空间还剩下2个字符，然后再次碰到select函数，默认情况下如果键盘内核空间字符数大于1，select是不会阻塞键盘描述符的。结果也印证了，又read了2个字节，并没有堵塞。<br />&nbsp; &nbsp; &nbsp; 综上所述select是可以看见内核空间的缓冲区的。那到底能不能看见用户空间缓冲区呢？我们换成stdio流的i/o函数继续实验。<br /><br />--------------------------------------------------------------------<br />stdio流的i/o函数使用select函数的程序如下：<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130115123858.jpg" width="542" height="476" alt="" /><br /><br />程序用stdio流的getc函数从键盘读数据，运行结果如下：<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130115124153.jpg" width="336" height="68" alt="" /><br />&nbsp; &nbsp;我们输入5个字符(1 2 3 4 \n),结果只输出了1个字符，然后就阻塞了。我们分析一下,首先输入5个字符，这5个字符被放入用户缓冲区，因为最后一个是换行符并且stdio面对设备使用行缓冲机制，所以这5个字符马上接着被从用户缓冲区刷入内核缓冲区。然后调用select函数，select函数发现内核空间中有数据，于是不阻塞返回。接着getc函数从用户空间输出缓冲区取一个字符，因为用户空间输出缓冲区没有数据，于是把内核空间的数据调入一行给用户空间输出缓冲区，然后getc返回。接着又碰上select函数，因为内核缓冲空间的数据已经被放入用户空间输出缓冲区了，所以内核缓冲没有数据，那么select认为键盘没有准备好，所以阻塞。虽然阻塞了，但需要注意的时候这个时候，用户空间是有4个字符数据的，被select函数无视了。<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130115125757.jpg" width="518" height="297" alt="" /><br />&nbsp; &nbsp; &nbsp;接下来假设我们再输入2个字符(1 \n),将会发生什么呢？<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130115130050.jpg" width="330" height="158" alt="" /><br />&nbsp; &nbsp; 输出一对东西，这是怎么回事，我们继续分析。当输入2个字符(1 \n)的时候，内核空间缓冲没有数据，用户空间输出缓冲有4个字符。2个字符根据上一段同样原理，被刷入内核空间缓冲区。select函数被调用，发现有2个字符，于是不阻塞返回。getc函数从用户输出缓冲取出一个字符，打印stardard... --2然后返回。再次循环，调用select函数。关键来了，这里跟上次不一样了。这个时候内核空间的缓冲中还有上次遗留的2个字符，所以依然不阻塞返回，调用getc函数继续打印。。。这里的关键是，getc函数。getc函数在用户输出缓冲中有数据的时候，不会把内核空间缓冲中的数据移入用户空间的输出缓冲，使得内核空间缓冲一直留有数据。这将会持续到用户空间输出缓冲的数据被取完为止。所以上述奇怪的打印结果就可以解释的了。<br />综上所述，select函数确实是看不见用户空间缓冲的寻在的。<br /><br /><br />所以如果在使用select函数的时候，要谨慎使用stdio流函数。<img src ="http://www.cppblog.com/mysileng/aggbug/197284.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mysileng/" target="_blank">鑫龙</a> 2013-01-15 13:09 <a href="http://www.cppblog.com/mysileng/archive/2013/01/15/197284.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>进程并发服务器中，sigchld信号引发的血案？(原)</title><link>http://www.cppblog.com/mysileng/archive/2013/01/11/197202.html</link><dc:creator>鑫龙</dc:creator><author>鑫龙</author><pubDate>Fri, 11 Jan 2013 10:54:00 GMT</pubDate><guid>http://www.cppblog.com/mysileng/archive/2013/01/11/197202.html</guid><wfw:comment>http://www.cppblog.com/mysileng/comments/197202.html</wfw:comment><comments>http://www.cppblog.com/mysileng/archive/2013/01/11/197202.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mysileng/comments/commentRss/197202.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mysileng/services/trackbacks/197202.html</trackback:ping><description><![CDATA[&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp;转载请注明 出自：http://www.cppblog.com/mysileng/archive/2013/01/11/197202.html<br /><br />&nbsp; &nbsp; &nbsp;讨论两个由sigchld信号引起的血案问题，讨论的环境是服务端的并发程序。我们先把最原始的服务器端并发程序模型贴出来：<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111183647.jpg" width="658" height="849" alt="" /><br />&nbsp; &nbsp; &nbsp;以上是服务器端程序，我们先不看被注视掉的部分，程序对于每一个accept的TCP连接会产生一个子进程，交给子进程去处理。而子进程其实并不做什么，直接睡眠3秒就结束。可以想象这样当子进程exit以后，会给父进程发sigchld信号，通知父进程自己挂了。但是在我们的父进程中，我们对于sigchld信号采用默认处置(忽略)。结果可想而知就是来一个连接，就产生一个僵死进程。我们运行程序3次，看看是否会得到3个僵死进程。<br />服务器端运行程序，被某客户端连接3次：<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111184329.jpg" width="307" height="129" alt="" /><br />客户端运行程序执行3次，并查看进程情况:<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111184500.jpg" width="583" height="218" alt="" /><br />&nbsp; &nbsp; 首先声明，我们用客户端可以查看服务器端进程的原因是，我们把客户端和服务端放在了一台电脑上进程本次试验。我们并不关心cli客户端的具体实现，因为服务器并不从客户端获取任何信息。<br />&nbsp; &nbsp; 从结果可知，果然服务端果断产生了3个僵死进程。接下来我们加上对sigchld的处理程序。但加上以后也将产生我们的第一个血案：<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111185827.jpg" width="608" height="809" alt="" /><br />白色为客户端，黑为服务端:<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111190006.jpg" width="582" height="656" alt="" /><br />&nbsp; &nbsp; 可见，僵死进程的问题已经解决，但还有个潜在的隐藏危机。<br />&nbsp; &nbsp; PHOSIX对于向accept这种慢速的系统系统调用有一个基本规则(apue,unp都有涉及)：当进程阻塞于某个慢系统调用的时候(我们的程序是accept)，当进程捕捉到某个信号(我们的程序是sigchld)，并从信号的处理函数返回时(我的程序是deal函数),进程不再阻塞与之前的慢速系统调用，而是返回一个EINTER错误。<br />&nbsp; &nbsp; &nbsp;对于上面的这个规则，各个操作系统的对待方式是不同的。有的操作系统返回EINTER以后，就会自动重启之前的慢速系统调用而继续，有些则不会自动重启。对我们实验程序的这个操作系统环境(centos5.5)，从结果来看,因为并没打印"accept error"并退出程序，我猜想，centos5.5应该是会自动重启慢速系统调用的。也就是说在这里我因为操作系统的优秀，躲过一劫(躲过第一次血案)。但为了可移植性我们应该改进程序为以下实现：<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111191225.jpg" width="654" height="907" alt="" /><br />&nbsp; &nbsp; 我的改动主要集中在对accpt的错误处理里面。接下来阐述另外一个血案<br />----------------------------------------------------------------------------<br />&nbsp; &nbsp; 我们继续沿用上述的最后一次服务端程序来进行接下来的实验，现在我们编写一个客户端程序，客户端一次性跟服务端申请5个连接。客户端的程序如下:<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111191948.jpg" width="669" height="634" alt="" /><br />&nbsp; &nbsp;整个程序的构架大概如下:<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111192148.jpg" width="681" height="179" alt="" /><br />&nbsp; &nbsp; 这里客户端最需要注意的是程序的最后一句并不是一个个close所有的套接字描述符，而是调用exit程序结束进程。根据APUE描述，exit系统调用会执行关闭该进程所有描述符的操作，也就是说客户端的所有描述符，包括套接字描述符也被几乎同时关闭了。也就是说服务端的由监听进程产生的所有处理子进程也会在几乎同时死掉。那么就会在几乎同时给父进程发送sigchld信号。情况如下:<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111192558.jpg" width="686" height="323" alt="" /><br />&nbsp; &nbsp; 血案即将发生。请注意，根据APUE对于信号在1-31之内的的信号，因为历史原因，是不可靠信号，也就说，SIGCHLD信号在被递送到正在阻塞SIGCHLD信号的进程时，是不会排队的，而是会被系统压缩。上述问题就是当5个sigchld信号几乎同时到达父进程时，只有第一个能顺利被父进程的信号处理函数处理。又因为被signal/sigaction设置的信号处理函数会自动阻塞正在处理的信号这一原则，接下来没被处理的4个sigchld信号，被排在了父进程门口。不巧的是，sigchld又是不可靠信号，结果是4个sigchld被压缩成一个sigchld信号。这就导致信号的丢失。也因为丢失了3个sigchld信号，就会产生3个僵尸进程。你说这是不是一个名符其实的血案。接下来我们实验一下：<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130111193441.jpg" width="844" height="243" alt="" /><br />&nbsp; &nbsp; 可以清晰的看到结果如预期，所有出现信号丢失导致3个僵死进程。那么怎么解决这个问题呢？~。。。。<img src ="http://www.cppblog.com/mysileng/aggbug/197202.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mysileng/" target="_blank">鑫龙</a> 2013-01-11 18:54 <a href="http://www.cppblog.com/mysileng/archive/2013/01/11/197202.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于线程与信号处理函数获得同一把互斥锁的问题 (原)</title><link>http://www.cppblog.com/mysileng/archive/2013/01/05/196971.html</link><dc:creator>鑫龙</dc:creator><author>鑫龙</author><pubDate>Sat, 05 Jan 2013 05:46:00 GMT</pubDate><guid>http://www.cppblog.com/mysileng/archive/2013/01/05/196971.html</guid><wfw:comment>http://www.cppblog.com/mysileng/comments/196971.html</wfw:comment><comments>http://www.cppblog.com/mysileng/archive/2013/01/05/196971.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mysileng/comments/commentRss/196971.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mysileng/services/trackbacks/196971.html</trackback:ping><description><![CDATA[转发请注明出处：http://www.cppblog.com/mysileng/admin/EditPosts.aspx?postid=196971<br />&nbsp; &nbsp;&nbsp;刚写了程序发现点问题。假设一个程序有多个线程，有一个全局互斥锁M....在某线程A获得锁以后，这个时候来了一个信号(假设这个信号注册了自己的处理程序)，那么需要进入信号处理程序，进入以后信号处理处理程序也要获得这个锁。问题来了？会死锁么？<br />&nbsp; &nbsp; 我们知道同一线程如果重复申请同一个互斥锁那么必然会死锁？这里问题转换到信号处理函数跟之前的线程A会是同一个线程上下文么（即是同一个线程么）？我们试验一下。实验之前需要明确几点：<br />&nbsp; &nbsp; 1.根据APUE 12.8，进程的处理函数与处理方式是进程中所有线程共享的。<br />&nbsp; &nbsp; 2.根据APUE 12.8，如果进程接收到信号，该信号只会被递送到某一个单独线程。一般情况下由那个线程引起信号则递送到那个线程。如果没有线程引发信号，信号被发送到任意线程。<br /><br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/1.jpg" width="510" height="539" alt="" /><br /><br />&nbsp; &nbsp; &nbsp;上面程序首先共享了一个共同的SIGUSR1信号处理函数，主控线程A然后产生一个线程B，线程B首先获得全局互斥锁，然后运行一个长5秒的程序，然后释放锁。在B运行了大概2秒的时候，线程A给本进程(注意是进程，而不是某线程)发送一个SIGUSR1信号。此时，会死锁么？编译运行看结果.<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/threa1d2.jpg" width="538" height="788" alt="" /><br />&nbsp; &nbsp; 连续运行了5次，都没有死锁。我们分析一下，当进程接收信号时，进程把信号并没有递送给线程B(因为没有产生死锁)，也就是说是不是这个递送过程被优化了？还是我们真的运气好？需要进一步探查。接下来我们指定把信号递送给线程B，看会不会死锁。<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130105134437.jpg" width="506" height="535" alt="" /><br />&nbsp; &nbsp; 接下来编译运行：<br /><img src="http://www.cppblog.com/images/cppblog_com/mysileng/QQ截图20130105134639.jpg" width="508" height="121" alt="" /><br />&nbsp; &nbsp; 不出意料，果然死锁了。<br />&nbsp; &nbsp; 也就是说，关于线程与信号处理函数获得同一把互斥锁的问题，关键是看信号被递送给了那个线程，如果信号是被递送给了获得锁的那个线程，就会死锁，如果不是之前获得锁的线程，程序就继续运行。<img src ="http://www.cppblog.com/mysileng/aggbug/196971.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mysileng/" target="_blank">鑫龙</a> 2013-01-05 13:46 <a href="http://www.cppblog.com/mysileng/archive/2013/01/05/196971.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>