Chinaunix首页 | 论坛 | 博客
  • 博客访问: 573618
  • 博文数量: 493
  • 博客积分: 2891
  • 博客等级: 少校
  • 技术积分: 4960
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-17 17:11
文章分类

全部博文(493)

文章存档

2010年(493)

分类:

2010-05-12 19:38:19

1 现象:问题描述
在socket单元测试时,使用select时候传入大的文件描述符(大于1024),会出现溢出情况,出现 segment fault (段错误)

2 关键过程:根本原因分析
我们使用以下代码分析一下原因,看sgement fault错误在哪里发生的:
1 编译以下文件,对socket相关的函数进行单元测试一下.
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
        int socket = atoi(argv[1]);
        int a = 10; // sockset之前的内存区
        int b = 10; // sockset之前的内存区
        fd_set sockset;
        int c = 10;  // sockset之后的内存区
        int d = 10; // sockset之后的内存区
        FD_ZERO(&sockset);
        FD_SET(socket, &sockset);
        printf("socket : %d, a = %d, b= %d, c= %d, d= %d\n", socket, a, b, c, d);
        return 0;
}
2 运行,传入参数 试试看
<216 ims202 [z46646] :/home/z46646/experiment>./a.out 1065
socket : 1065, a = 10, b= 522, c= 10, d= 10
<225 ims202 [z46646] :/home/z46646/experiment>./a.out 1069
socket : 1069, a = 10, b= 8202, c= 10, d= 10
<226 ims202 [z46646] :/home/z46646/experiment>./a.out 1079
socket : 1079, a = 10, b= 8388618, c= 10, d= 10
<227 ims202 [z46646] :/home/z46646/experiment>./a.out 1080
socket : 1080, a = 10, b= 16777226, c= 10, d= 10
<228 ims202 [z46646] :/home/z46646/experiment>./a.out 1087
socket : 1087, a = 10, b= -2147483638, c= 10, d= 10
<229 ims202 [z46646] :/home/z46646/experiment>./a.out 1088
socket : 1088, a = 11, b= 10, c= 10, d= 10
<230 ims202 [z46646] :/home/z46646/experiment>./a.out 1089
socket : 1089, a = 10, b= 10, c= 10, d= 10
从 1060 到 1087 之间 b 的值被修改, 1088时 a 的值 被修改 。说明fs_set修改了sockset之外的内存。
如果fd_set sockset;前后没有设置 a,b,c,d变量,FD_SET就会修改到函数栈以外的内存,造成sgement fault错误。
相关头文件: /usr/include/sys/select.h 和/usr/include/bits/select.h 

溢出的原因应该在这一段,懂汇编的读者可以分析一下:
# define __FD_SET(fd, fdsp) \
  __asm__ __volatile__ ("btsl %1,%0"           \
   : "=m" (__FDS_BITS (fdsp)[__FDELT (fd)])       \
   : "r" (((int) (fd)) % __NFDBITS)        \
   : "cc","memory")
3 SuSe 等linux上select(), fd_set, FD_ZERO,和FD_SET等的实现是这样的:
fd_set是一个128字节的位表, 128*8 = 1024, 每个位代表一个socket.
 fd_set sockset;
执行:FD_SET(0, sockset); 后,sockset的值如下:
   0000 0001, 0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,....
执行:FD_SET(1, sockset); 后,sockset的值如下:
   0000 0010, 0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,....
执行:FD_SET(8, sockset); 后,sockset的值如下:
   1000 0000, 0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,....
执行:FD_SET(1088, sockset); 后,????
        ????????,0000 0000, 0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,0000 0000,....
这里的值被修改->|<-sockset的起始地址
3 结论:解决方案及效果
由上面的分析,select的算法显然比较拙劣:
1.每次都要分配128字节的位表,FD_ZERO(&sockset)要清除128个字节的内存。
2.如果文件描述符大于1024,FD_ZERO会溢出,造成段错误。
使用poll可以代替select poll的个数由程序员自己设置,没有大文件描述符的限制:
阅读(733) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~