开始
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吧。