select 1024限制

文章主要来自彻底解释Linux select的1024限制(select真的受1024限制吗?不!),原作者通过源码和实验解释select真的是受1024限制吗,但是原作者的代码似乎有些问题(好像跑不通,但是思想表达的很明晰),这篇文章主要是对那篇文章的一个简单的解释,以及遇到的奇怪问题的记录。

一、源码

// include/uapi/linux/posix_types.h
#define __FD_SETSIZE    1024

typedef struct {
    unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;

这是linux源码,从源码上看确实限制为1024(看完源码真的以为是这里限制select为1024,但是那片文章的作者,给了新的解释,确实很有道理。)

二、FD_SET是否限制1024

    首先作者分析FD_SET是否会对1024进行越界检查,或者限制在1024 bit。但是原作者的代码表述的逻辑没什么问题,似乎是g++编译过程中对内存分布作了一定的改变,导致原作值的代码无法复线结果,下面代码作了一点修改,可以得到原作者的结果。

// 原作者代码
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
	int i = 2000, j;
	unsigned char gap = 0x12; // 该值作为锚点,被位运算覆盖。
	fd_set readfds;
	unsigned char *addr = &gap;
	int sd[2500];
	unsigned long dist;
	unsigned total;

	printf("gap value is :0x%x\n", gap);
	// dist的含义就是readfds和附近gap之间的空间大小,即readfds最大的可用空间。
	dist = (unsigned long)&gap - (unsigned long)&readfds;
	FD_ZERO(&readfds);
	// dist*8 + 1即让readfds越界1个bit。
	// 由于gap为0x12,二进制10010,越界1个bit,可以预期FD_SET会置位0x12的最低位。
	// 结果就是0x13
	for (j = 0; j < dist*8 + 1; j++) {
		sd[j] = j;
		FD_SET(sd[j], &readfds);
	}
	printf("j %d .", j);
	printf("after FD_SET. gap value is :0x%01x   bytes space:%d\n", gap, dist);
}
// 修改后代码
#include <stdio.h>
#include <stdlib.h>

struct mst{
    fd_set readfds;
    unsigned char gap = 0x12;

};

int main(int argc, char **argv)
{
	int i = 2000, j;
    mst st;
	unsigned char *addr = &st.gap;
	int sd[2500];
	unsigned long dist;
	unsigned total;

	printf("gap value is :0x%x\n", st.gap);
	// dist的含义就是readfds和附近gap之间的空间大小,即readfds最大的可用空间。
	dist = (unsigned long)&(st.gap) - (unsigned long)&(st.readfds);
	FD_ZERO(&(st.readfds));
	// dist*8 + 1即让readfds越界1个bit。
	// 由于gap为0x12,二进制10010,越界1个bit,可以预期FD_SET会置位0x12的最低位。
	// 结果就是0x13
	for (j = 0; j < dist*8 + 1; j++) {
		sd[j] = j;
		FD_SET(sd[j], &(st.readfds));
	}
	printf("j %d .", j);
	printf("after FD_SET. gap value is :0x%01x   bytes space:%d\n", st.gap, dist);
}

    作者是希望gap作为readfds的锚点(或者说“哨兵”),而readfds是128字节1024bit,那么当通过FD_SET设置1025bit的时候会越界到gap的bit上,从而使得gap从0x12变为0x13.但是源代码的问题是,readfds和gap在内存上并不相连,

 理论上来说,gap地址是0x7fffffffb44f,而readfds 128字节,地址应该是0x7fffffffb44f - 0x80,但是显然不是。不知道为什么,感觉是不是g++编译过程中的问题,或者,链接GUN动态库时的问题。目前还不清楚是什么原因。

    修改后就可以得到期望的结果

    这个示例是为了说明,FD_SET并不会限制在1024 bit内,并且不会检查数组越界问题。

----------------------------------------------------------------------------

其实这里开始还遇到一个问题,就是core dump文件没办法在分区创建:If you run the core file in a mounted drive.The core file can't be written to a mounted drive but must be written to the local drive.

-------------------------------------------------------------------------------

三、突破1024限制

// 原本代码
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>

#define SIZE 1200
// 这些变量不再放在栈上,以防被覆盖。
int i = 1001, j;
int sd[SIZE];
struct sockaddr_in serveraddr;
int main(int argc, char **argv)
{
	// 使readfds在第一个,覆盖掉我们不再care about的内存.
	fd_set readfds;
	int childfd;

	FD_ZERO(&readfds);
	for (j = 0; j < SIZE; j++) {
		sd[j] = socket(AF_INET, SOCK_STREAM, 0);
		serveraddr.sin_family = AF_INET;
		serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
		serveraddr.sin_port = htons(i++);
		bind(sd[j], (struct sockaddr *) &serveraddr, sizeof(serveraddr));
		listen(sd[j], 5);
		FD_SET(sd[j], &readfds);
	}

	while (1) {
		// select 超过1024的...
		if (select(1100, &readfds, 0, 0, 0) < 0) {
			perror("ERROR in select");
		}
		for (j = 0; j < SIZE; j++) {
			if (FD_ISSET(sd[j], &readfds)) {
      			childfd = accept(sd[j], NULL, NULL);
				printf("#### %d\n", j);
      			close(childfd);
			}
		}
	}
}

说实话,原本代码有点不知道他在写些什么,但是意思到时可以理解。

// 修改代码
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>

#define SIZE 1200
// 这些变量不再放在栈上,以防被覆盖。
int i = 1001, j;
int sd[SIZE];
//struct sockaddr_in serveraddr;
int main(int argc, char **argv)
{
	// 使readfds在第一个,覆盖掉我们不再care about的内存.
	fd_set readfds;
	int childfd;

	FD_ZERO(&readfds);
	for (j = 0; j < SIZE; j++) {
		sd[j] = socket(AF_INET, SOCK_STREAM, 0);
		
		if(sd[j] < 0){
			perror("socket");
		}

		struct sockaddr_in serveraddr;
		serveraddr.sin_family = AF_INET;
		serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
		serveraddr.sin_port = htons(i++);
		bind(sd[j], (struct sockaddr *) &serveraddr, sizeof(serveraddr));
		listen(sd[j], 5);
		FD_SET(sd[j], &readfds);
	}


	while (1) {
		// select 超过1024的...
		if (select(1100, &readfds, 0, 0, 0) < 0) {
			perror("ERROR in select");
		}
		for (j = 0; j < SIZE; j++) {
			if (FD_ISSET(sd[j], &readfds)) {
      				childfd = accept(sd[j], NULL, NULL);
				printf("#### %d\n", j);
      				close(childfd);
			}
		}
	}

	return 0;
}

运行后(别忘了设置进程可开启的最大文件描述符数量),通过

nc 127.0.0.1 2025

 可以看到,确实打开了超过1024项文件。那么为什么呢,

    在源码里,确实定义了1024那个常量,但是在select实际使用中并没有受到那个常量的限制

size = FDS_BYTES(n);
bits = stack_fds;
if (size > sizeof(stack_fds) / 6) {
	/* Not enough space in on-stack array; must use kmalloc */
	ret = -ENOMEM;
	if (size > (SIZE_MAX / 6))
		goto out_nofds;

	alloc_size = 6 * size;
	bits = kvmalloc(alloc_size, GFP_KERNEL);
	if (!bits)
		goto out_nofds;
}

运行中首先是在栈上尝试构建6个bitmap,如果大于栈上的内存,就会重新malloc。但是受到SIZE_MAX限制。

然后从用户台拷贝数据的时候

get_fd_set(n, inp, fds.in)


static inline
int get_fd_set(unsigned long nr, void __user *ufdset, unsigned long *fdset)
{
	nr = FDS_BYTES(nr);
	if (ufdset)
		return copy_from_user(fdset, ufdset, nr) ? -EFAULT : 0;

	memset(fdset, 0, nr);
	return 0;
}

依然没有受到1024的限制,用到的是传入的文件数量n,然后拷贝的时候是给出用户态数据地址和 内核数据地址,和要拷贝数据的字节数量。所以,实际上就如那篇文章中说的:可以超过1024,但是FD_SET超过1024的值,会造成越界,需要承担越界之后可能出现的各种后果。但同时,也不会无限制的允许监听文件的数量,SIZE_MAX限制文件描述符的数量。

  • 22
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值