Archive

Posts Tagged ‘coding’

诡异的WriteFile和完成端口行为

April 6th, 2012 No comments

在写代码的过程中发现了一个很奇怪的现象。代码的主要意图是利用完成端口来获取写文件操作是否完成。

所以在判断了WriteFile的返回值以后,如果返回TRUE,就直接获取当前写入的字节。而如果返回值为FALSE,那么GetLastError() == ERROR_IO_PENDING的话等候完成端口的事件报告,并得到写入的字节数。 本来以为没有什么问题。但是测试的时候发现了一个问题。那就是即便WriteFile返回TRUE,并且写入字节数也和数据大小匹配,并且GetLastError() == 0. 但是完成端口那个地方仍然有事件报告,这个事件对我来说是一个未预期的事件。所以只能做如下判断

if( ( returnValue && GetLastError() == 0 ) ||
    (!returnValue && GetLastError() == ERROR_IO_PENDING) )
{
    //check IOCP event
}
else
{
    return err;
}

我现在的测试环境是WINXP+SP2,不知道其他的windows版本会有何表现

Categories: programming Tags: , ,

GetTickCount的实现

April 6th, 2012 No comments

昨天突然想看看GetTickCount的实现,于是借助调试器看了一下GetTickCount的汇编代码。那是一个相当的简单

_GetTickCount@0:
7C80934A mov edx,7FFE0000h
7C80934F mov eax,dword ptr [edx]
7C809351 mul eax,dword ptr [edx+4]
7C809354 shrd eax,edx,18h

简单到如此地步让我觉得相当诡异,只有几个赋值,乘,位移之类的操作怎么就把当前的时钟滴答数搞到手了呢?看了这篇文章以后大致知道内核中估计会定期更新0x7FFE0000这个地址上的内容,然后GetTickCount只需要获取这个地址上的值,再经过简单的计算就可以得出答案了。

Categories: programming Tags: , ,

一个关于UDP数据发送错误的问题

March 8th, 2012 No comments

  为了能支持新项目的DMSCC和LSCP协议,决定增加UDP的功能。加上以前在系统上使用的是IOCP(win)/epoll(linux),所以新增加的UDP也尽量向采用这个机制,同时沿用以前的框架,那么外部对于socket server的使用基本保持一致。

  完成代码以后测试的时候发现了一个问题,就是经过一段时间的测试以后,UDP server就不在接收数据了。检查日志的时候发现原来是UDP server socket收到了一个报错,而我之前很蠢的认为如果udp server socket报错就代表这个socket不可再用了。后来才明白这个错误是因为server socket尝试发送一个udp数据包到client的时候因为client可以能已经关闭了socket,或者其他的原因造成数据包不可到达。这个时候系统会收到一个ICMP消息告知主机消息不可达。而这个时候IOCP就会告知说出现一个错误(在windows上错误码是1234)。当收到这个错误告知以后,再次调用WSARecvFrom即可继续接收新的udp消息。

udp 数据包不可达,收到一个ICMP消息

Categories: programming Tags: ,

为何编译时的头文件所在的目录也需要可执行权限?

December 15th, 2011 No comments

  开发机是大家共用的,一人一个账号。刚才突然发现昨天还可以正常build的source code无法编译通过了。非常的纳闷,g++的输出如下:

common_conf.h:181:23: error: /usr/local/include/unistd.h: Permission denied
common_conf.h:182:26: error: /usr/local/include/sys/types.h: Permission denied

其实unistd.h根本不在/usr/local/include文件夹下面,第二,这个文件以及路径上都是由可读权限的。为何会出现Permission Denied呢?查看了/usr/local/目录下面的其他文件夹的属性以后,我猜是因为include没有可执行权限造成的这个错误。于是

chmod +x /usr/local/include

以后发现一切正常了。不过现在我还是不明白为何需要/usr/local/include有执行权限才可以正常编译下去,为何可读的权限不行。还有为何unistd.h不在/usr/local/include下的时候会出现读不到(这个时候是因为Permission Denied)而停止编译,但是这个文件明明可以在/usr/include下面找到的,实在有点想不明白。

Categories: mutter, programming Tags:

一个愚蠢的错误,inotify handle不可写

December 8th, 2011 No comments

以前的同事的一个模块在程序退出的会卡住,也就是pthread_join不返回。于是检查其原因。发现这个模块是一个线程类,用于监视文件系统上的文件事件。该类并没有加入退出的逻辑。该类的run函数中使用read函数来获得文件系统的消息。为了能正常退出,我添加了一些退出的逻辑。大致如下

void FileEventMonitor::run()
{//这里是在一个新的线程中运行的
	while(!mbQuit)
	{
		int r = read( inotifyHandle , buffer, buffersize );
		if( this->bQuit) //增加了判断,如果要退出的话就break
			break;
		/process the event
	}
}
void FileEventMonitor::stop()
{
	bQuit = true;
	write(inotifyHandle,"q",1);
	pthread_join();//等待线程退出
}

我天真的以为直接想inotifyHandle中写入一点数据,read函数就可以退出,然后由于bQuit被置成了true,那么线程就会退出了。结果测试的时候发现还是退不出去,而我又没有判断write的返回值,所以一开始我一直认为是其他的什么怪异的原因。知道后来加上了对write返回值的判断才发现是写失败了。不过很是奇怪的是write失败后的errno是9,也就是bad file number。

  很明显,这条路走不通了,于是我在read之前加上了select,只有当有数据的时候才去read,并且加上一个timeout时间,例如500ms(这样的话service退出也不会等待太久)。这样就解决了这个无法正常退出的问题。

代码如下:

void FileEventMonitor::monitor()
{
    fd_set fds;
    struct timeval tv;
    while(!mbQuit)
    {
        tv.tv_sec = 0;
        tv.tv_usec = 5000 * 1000; //500ms
        FD_ZERO(&fds);
        FD_SET(mEventhandle,&fds);
        int r = select( mEventhandle + 1 , &fds, NULL , NULL , &tv);
        if( r < 0)
        {
            //log error
            break
        }
        else if( r == 0 )
        {
            continue;
        }
	//process the event
    }
}

在stop函数中只需要简单的吧mbQuit置成true就可以了。

不过我现在还有点疑问,既然write的时候返回bad file number,那么为何可以使用select来检测这个handle上是否有数据可读,select不也是操作文件描述符的吗?有点不太懂。

   不过,至少退出的问题是解决了。今天记录一下这个问题,希望以后不要想当然的认为某个调用就一定会成功。

Categories: programming Tags:

内存泄露 (续)

October 29th, 2011 No comments

  在现场的一个服务有巨大的内存泄露的问题,最近一直都在检查这个问题。虽然看起来还有一点泄露,不过已经能够让现场可以撑得更久一点了。

  现场的一个播流的服务(实际上时一个速率可控的http下载的服务器端),由于前一段时间性能不能达到客户的要求,而大幅修改过很多的代码。而且又需要立马上线,结果不能很好的测试就把匆忙修改的程序部署到了现场。现在想起来真实悲催啊,因为性能问题而修改累了一段时间,现在有需要给这个没有充分测试的程序做扫尾动作。

  当现场报说有大量内存泄露以后,我就开始着手使用分析工具来看看泄露到底是什么地方引起的。 我选择了google performance tool来查看内存泄露的点. 安装google perf tool测试一段时候,根据google perftool的提示和code review找到了一些内存泄露的代码。但是奇怪的就是修复了这些泄露点以后重新build一个版本做压力测试发现还是有内存泄露。这个让我觉得很是奇怪,再次使用google perftool测试,但是该工具没有报告任何的内存泄漏点,这个让我一度怀疑这个工具是有问题的。不过后来一想,我们的应用用到了很多的stl class,特别是std::map std::list等东西, 如果这些实例析构以后所占用的内存会被释放,而google perftool是要在程序退出以后才能报告是否有内存泄露。如果一个程序在运行的过程中不断的向一个list或者map插入新的记录但是忘了删除的话,这个依然是一个内存泄漏点。为了验证我的想法,我在google perftool的代码加入了一个signal的响应函数,当接收到这个siganl的时候就做一次内存泄露报告。然后使用这个修改后的google perftool进行测试,程序在大压力的环境下跑了半个小时以后,把所有的测试客户端都停掉(防止在report memory leak的时候还有代码需要做内存分配/释放操作)。然后查看内存泄露报告,发现确实如我所想,内存泄露大都来之std::map中的record没有删除而造成的。

  当我以为这些内存泄露点都修复好了程序就应该没有什么问题了,接下来的压力测试让我觉得有点摸不着头脑了。程序在大压力的情况下跑了48+小时后发现有400M+的内存泄露,停掉所有的客户端以后内存使用并不会回到最初状态。而在此使用google perftool进行检测却找不到有内存泄漏点。迷惑了很长时间以后google了一番找到了http://www.nosqlnotes.net/archives/105这篇文章,也就是GLIBC的内存分配/释放的缺省行为可能造成内存不会被正常交还给kernel(当然我的程序是否是这个问题还需要进一步确认)。

  查找完内存泄露点以后觉得很有必要做一个记录,也好给自己做一个提醒。

1.  malloc / new 以后没有free/delete。

2.  加入到map list vector等容器中的record没有及时删除

3.  智能指针的循环引用,例如A和B是两个实力,其中A有一个智能指针指向B,而B也有一个智能指针指向A。如果不主动调用A或者B的资源释放方法的话A和B均不会被析构

4.  还有就是没有考虑到所有的资源释放情形,导致在特殊情况下资源释放的方法不能被调用到

     例如,在调用了epoll_ctl(…, EPOLL_CTL_ADD , …)以后没有检查返回值,在某个情况下这个函数可能会返回错误,这个时候就应该调用资源释放函数(这个socket已经不可用了)

5. 还有啥?想到了再补充一下…

Categories: programming Tags: ,

内存泄露啦

October 19th, 2011 No comments

内存泄露真的是一个很令人纠结的问题,于是使用了诸如智能指针的东西来避免这个问题,但悲哀的是就连这样还是造成了内存泄露。仔细查看源代码以后发现了问题的所在。

程序使用的智能指针是基于引用次数的,也就是说当某一个object被引用的次数>0的时候,这个object就会保留在内存中。当引用次数减少到0的时候,也就是没有任何一个地方在使用这个object的时候这个object就会被删除。

在程序中有一个地方对于这个智能指针的使用不当导致了引用次数不能正常减少到0,引起了内存泄露。程序中有A和B两个对象,其中B是A的一个member。同时B有一个智能指针指向A(需要在运行的时候使用A的部分功能)。在一切正常的情形下面,当调用A的close的时候,A会调用B的close,同时B会将指向A的智能指针置空(也就是不指向A了),这个时候就是正常的不会产生内存泄露。但是异常的情况下,A的close调用B的close的时候,B不会清除对A的引用,这个时候由于A还有人引用–B还有一个指针指向A,所以A不会被析构,导致了内存的泄露。

这个问题提醒我即便使用了智能指针也要小心的使用,及时清除对对象的引用。还有就是最好不要交叉引用–A引用B,同时B引用A。

Categories: programming Tags: ,