31 January 2015

开始

select()经常作为deMultiplex的分发者,也是reactor模式的重要部分,在编程中会经常遇到,当然java io 编程是不会直接touch到这一部分的。

问题来源于我们的一段JNI的c代码,主要是做sctp的socket编程的,这段JNI代码是在weblogic容器里面被调用。现象是在weblogic容器里面这段c代码表现异常,甚至导致jvm crash。

定位问题

这个过程比较纠结也花费了很长时间。

首先我们写了模拟程序拿到容器之外去运行,发现始终都是成功的,所以我们把怀疑的对象定位在weblogic,可能是权限问题?但是有时能够成功的现象排除掉了这种可能。可能是容器对jni的调用做了什么特别的处理,导致在容器里面出现了某种不可预知性?。。。

最终组内高人出马,找到了根本问题所在: linux默认所能操作的select的fd个数最大只能是1024个,也即FD_SETSIZE = 1024, 如果超出的话就会内存非法访问。

这也就解释了在容器打开fd很多的情况下会失败,而小的java程序始终成功的原因。

select()

标准的select的定义是这样的

int select(int nfds, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

其中 nfds: 是所有监控fd中值最大的那个值+1,注意是值而不是fd的个数。
返回值可能是: 返回正数,即所有事件都ready的总句柄数目之和;或者返回0,即超时;或者返回-1,即出错。

为了操作方便,系统还提供了一系列的宏操作fd,比如:

void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
int FD_ISSET(int fd, fd_set *fdset);

这里简单说下FD_SET的操作,就是一个比特位数组的访问操作。

那一段相关代码大概是这样,网上随处可以找到类似的:

fd_set fds;
struct timeval tv;

while(res == 0) {
	
	tv.tv_sec = 60;
	tv.tv_usec = 0;

	FD_ZERO(&fds);
	FD_SET(fd1, &fds);
	FD_SET(fd2, &fds);
	
	int fdmax = fd1 > fd2 ? fd1 : fd2;
	res = select(fdmax + 1, &fds, NULL, NULL, &tv);
	......  
}

出问题的地方是res有时会返回一些随机的整数,而且有时jvm会crash。当然偶尔也是能够成功的。

解决问题

首先我们想简单点直接修改这个宏FD_SETSIZE的值,但是没有奏效。

后来在深入认识select操作的基础上我们只能采取以下做法,其实就是分配了更多的连续空间,为了保证fd在超过1024的情况下操作的正确,见示例代码:

#define MAX_FD_NUM 8

fd_set fds[MAX_FD_NUM];
struct timeval tv;

while(res == 0) {
	
	tv.tv_sec = 60;
	tv.tv_usec = 0;
	
	for(int i=0; i< MAX_FD_NUM; i++)
		FD_ZERO(&fds[i]);

	FD_SET(fd1%FD_SETSIZE, &fds[fd1/FD_SETSIZE]);
	FD_SET(fd2%FD_SETSIZE, &fds[fd2/FD_SETSIZE]);
	
	int fdmax = fd1 > fd2 ? fd1 : fd2;
	res = select(fdmax + 1, &fds[0], NULL, NULL, &tv);
	......  
}

if(!FD_ISSET(fd1%FD_SETSIZE, &fds[fd1/FD_SETSIZE])) {
		......;
	}

后记

其实select的1024问题是个老问题了,由于好久没有写底层代码,而且没有亲历过始终不能痛得彻底 :)

select已经是上个世纪的东西了,当然每个平台都支持是其优势,现在都是epoll的天下,更多内容请自行google吧。



blog comments powered by Disqus