select(),FD_SET(),FD_CLR(),FD_ISSET(),FD_ZERO()

头文件以及函数原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

FD_SET(int fd, fd_set *fdset);

FD_CLR(int fd, fd_set *fdset);

FD_ISSET(int fd, fd_set *fdset);

FD_ZERO(fd_set *fdset);

函数说明

select函数

函数描述:

英文:
The select() function indicates which of the specified file descriptors is ready for reading, ready for writing, or has an error condition pending. If the specified condition is false for all of the specified file descriptors, select() blocks, up to the specified timeout interval, until the specified condition is true for at least one of the specified file descriptors or until a signal arrives that needs to be delivered.

我的理解:

slecet()函数会给出哪个文件描述符准备好写,读,或者有错误挂起条件。如果对于所有的文件描述符都是false(就是没有发生写,读,或者错误挂起条件),select()函数就会阻塞。
阻塞的时间取决于timeout,如果在阻塞期间,有文件描述符为true(就是发生了写,读,或者错误挂起条件),或者或者需要传递的信号到达,select()函数就会结束阻塞(不论timeout时间有没有到)。

英文:
The select() function supports regular file descriptors, console descriptors, pipe descriptors, FIFO descriptors, socket descriptors, and communication port descriptors. A select() on file descriptors that refer to other types of file fails with errno set to EBADF.

翻译:
select()函数支持常规文件描述符、控制台描述符、管道描述符、FIFO描述符、套接字描述符和通信端口描述符。引用其他类型文件的文件描述符上的select()失败,errno设置为EBADF。

参数说明:

nfds:

The nfds argument specifies the range of file descriptors to be tested. The select() function tests file descriptors in the range of 0 to nfds-1.(nfds参数指示了要检测的文件描述符的范围。select()函数检测文件描述符的范围为,从0到nfds-1,所以如果文件描述符为fd要想检测fd,select中fd需要加1,select(fd+1,,,))

readfds:

If the readfds argument is not NULL, it points to an object of type fd_set that on input specifies the file descriptors to be checked for being ready to read, and on output indicates which file descriptors are ready to read. (如果readfds参数不为NULL,它将指向fd_set类型的对象,该对象在输入时指定要检查哪些文件描述符已准备好读取,在输出时指示哪些文件描述符已准备好读取。)

writefds: If the writefds argument is not NULL, it points to an object of type fd_set that on input specifies the file descriptors to be checked for being ready to write, and on output indicates which file descriptors are ready to write.(如果writefds参数不为NULL,它将指向fd_set类型的对象,该对象在输入时指定要检查那些文件描述符已准备写入,在输出时指示哪些文件描述符准备写入。)

exceptfds:

If the exceptfds argument is not NULL, it points to an object of type fd_set that on input specifies the file descriptors to be checked for error conditions pending, and on output indicates which file descriptors have error conditions pending.(如果exceptfds参数不为NULL,则它指向fd_set类型的对象,该对象在输入时指定要检查哪些文件描述符存在挂起的错误条件,在输出时指示哪些文件描述符具有挂起的错误条件。)

timeout:

If the timeout argument is not a NULL pointer, it points to an object of type struct timeval that specifies a maximum interval to wait for the selection to complete. If the timeout argument points to an object of type struct timeval whose members are 0, select() does not block. If the timeout argument is a NULL pointer, select() blocks until an event causes one of the masks to be returned with a valid (non-zero) value or until a signal occurs that needs to be delivered. If the time limit expires before any event occurs that would cause one of the masks to be set to a non-zero value, select() completes successfully and returns 0.(设置select()函数超时时间)

1
2
3
4
5
struct timeval
{
long tv_sec; //秒
long tv_usec; //微秒
};
函数返回值:

返回值:超时返回0;失败返回-1;成功返回大于0的整数,这个整数表示就绪描述符的数目。

注:

  1. If the readfds, writefds, and exceptfds arguments are all NULL pointers and the timeout argument is not NULL, select() blocks for the time specified, or until interrupted by a signal. If the readfds, writefds, and exceptfds arguments are all NULL pointers and the timeout argument is NULL, select() blocks until interrupted by a signal.
  2. On failure, the objects pointed to by the readfds, writefds, and exceptfds arguments are not modified. If the timeout interval expires without the specified condition being true for any of the specified file descriptors, the objects pointed to by the readfds, writefds, and exceptfds arguments have all bits set to 0.

FD_ZERO

一个 fd_set类型变量的所有位都设为 0

FD_CLR

清除某个位时可以使用

FD_ISSET

测试某个位是否被置位

FD_SET

设置变量的某个位置位

深入理解select

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd

  1. 执行fd_set set; FD_ZERO(&set); 则set用位表示是0000,0000。
  2. 若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
  3. 若再加入fd=2,fd=1,则set变为0001,0011
  4. 执行select(6,&set,0,0,0)阻塞等待
  5. 若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

例程:Linux串口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>

// 串口文件描述符
volatile int fd=-1;
// 读串口数据文件描述符集合
fd_set rd;
// 超时时间设置
struct timeval timeout={0,100};
//当前数据缓存区
unsigned char recive_data [2000]={0};

// 打开串口, 返回串口描述符
int OpenUart(char *dev_,int baudrate_)
{
speed_t speed;

int i=0;
int fdt,c=0,num;
struct termios oldtio,newtio;

speed = getBaudrate(baudrate_);
fdt=open(dev_,O_RDWR | O_NONBLOCK| O_NOCTTY | O_NDELAY);
if(fdt<0)
{
perror(dev_);
exit(1);
}
//save to oldtio
tcgetattr(fdt,&oldtio);
//clear newtio
bzero(&newtio,sizeof(newtio));
//newtio.c_cflag = speed|CS8|CLOCAL|CREAD|CRTSCTS;
newtio.c_cflag = speed|CS8|CLOCAL|CREAD;
// newtio.c_cflag &= ~CSIZE;
newtio.c_cflag &= ~CSTOPB;
newtio.c_cflag &= ~PARENB;
newtio.c_iflag = IGNPAR;
newtio.c_oflag = 0;
tcflush(fdt,TCIFLUSH);
tcsetattr(fdt,TCSANOW,&newtio);
tcgetattr(fdt,&oldtio);
return fdt;
}

// 波特率产生函数
static speed_t getBaudrate(int baudrate_)
{
switch(baudrate_) {
case 0: return B0;
case 50: return B50;
case 75: return B75;
case 110: return B110;
case 134: return B134;
case 150: return B150;
case 200: return B200;
case 300: return B300;
case 600: return B600;
case 1200: return B1200;
case 1800: return B1800;
case 2400: return B2400;
case 4800: return B4800;
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
case 230400: return B230400;
case 460800: return B460800;
case 500000: return B500000;
case 576000: return B576000;
case 921600: return B921600;
case 1000000: return B1000000;
case 1152000: return B1152000;
case 1500000: return B1500000;
case 2000000: return B2000000;
case 2500000: return B2500000;
case 3000000: return B3000000;
case 3500000: return B3500000;
case 4000000: return B4000000;
default: return -1;
}
}

// 读取串口数据
void ReadUart()
{
int read_num=0;
int retval;
FD_ZERO(&rd);
FD_SET(fd,&rd);
retval = select (fd+1,&rd,NULL,NULL,&timeout);
switch(retval)
{
case 0:
break;
case -1:
perror("select\n");
break;
default:
if(FD_ISSET(fd,&rd)&&((read_num=read(fd,recive_data,sizeof(recive_data)))>0))
{
//在这里处理串口读到的数据
}
break;
}
}