select
函数是在网络编程和文件I/O中广泛使用的一个系统调用,特别是在UNIX、Linux和类UNIX系统中。它允许程序监视多个文件描述符(file descriptors),以等待一个或多个文件描述符上的某些类型的事件发生(比如数据可读、写就绪、异常条件等)。当select
函数返回时,它会告诉程序哪些文件描述符已经准备好进行I/O操作。
函数原型
在POSIX兼容的系统中,select
函数的原型通常定义在<sys/select.h>
或<sys/time.h>
头文件中(具体取决于系统),其原型大致如下:
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数说明
- nfds:这是一个整数值,指定了文件描述符集合中最大文件描述符的值加1。这个参数用于确定
select
需要检查的文件描述符的范围。 - readfds:指向
fd_set
的指针,这个集合包含了所有需要被检查读条件的文件描述符。如果不需要检查读条件,可以设置为NULL。 - writefds:指向
fd_set
的指针,这个集合包含了所有需要被检查写条件的文件描述符。如果不需要检查写条件,可以设置为NULL。 - exceptfds:指向
fd_set
的指针,这个集合包含了所有需要被检查异常条件的文件描述符。如果不需要检查异常条件,可以设置为NULL。 - timeout:指向
timeval
结构的指针,指定了select
等待的最长时间。如果设置为NULL,则select
将无限期等待,直到至少有一个文件描述符准备好。
返回值
- 成功时,
select
返回准备好的文件描述符的总数(可能包括0,如果超时但没有任何文件描述符准备好)。 - 出错时,返回-1,并设置相应的errno以指示错误。
使用场景
select
函数常用于实现非阻塞的I/O操作,例如,在服务器程序中监听多个客户端连接。通过select
,服务器可以等待多个套接字(socket)上的数据,而无需为每个套接字创建单独的线程或进程。
然而,select
也有一些限制,比如它能监视的文件描述符数量有限(通常是1024),并且在文件描述符集合较大时,select
的效率可能会下降,因为它会遍历整个集合来查找哪些文件描述符已经准备好。为了解决这些问题,现代系统通常提供了更高效的替代方案,如poll
和epoll
(Linux特有)。
FD_ZERO
FD_ZERO是Linux及类Unix系统编程中用于操作文件描述符集合(fd_set)的一个宏。在对文件描述符集合进行设置前,必须对其进行初始化,以确保集合中不包含任何文件描述符。这是因为系统分配内存空间后,通常并不自动进行清空处理,如果不进行初始化,集合中的内容是未知的,可能会导致不可预测的行为。
FD_ZERO(fd_set *fdset);
这个宏接受一个指向fd_set
类型的指针作为参数,并将该集合中的所有位都清零,即清空集合中的所有文件描述符。
重要性
- 初始化:在使用
fd_set
集合之前,必须通过FD_ZERO
进行初始化,以确保集合是空的,从而避免未定义行为。 - 准备阶段:在调用
select
、poll
等函数之前,通常需要先使用FD_ZERO
清空fd_set
集合,然后通过FD_SET
等宏将需要监视的文件描述符添加到集合中。
与其他宏的关系
- FD_SET:用于在文件描述符集合中增加一个新的文件描述符。
- FD_CLR:用于在文件描述符集合中删除一个文件描述符。
- FD_ISSET:用于测试指定的文件描述符是否在该集合中。
#include <sys/select.h>
#include <stdio.h>
#include <unistd.h>
int main() {
fd_set readfds;
// 初始化fd_set集合
FD_ZERO(&readfds);
// 假设我们有一个文件描述符fd
int fd = 0; // 例如,标准输入的文件描述符
// 将文件描述符fd添加到readfds集合中
FD_SET(fd, &readfds);
// ...(后续可以使用select等函数来监视readfds集合中的文件描述符)
return 0;
}
FD_SET
FD_SET是Linux及类Unix系统编程中用于操作文件描述符集合(fd_set)的一个宏。它允许程序将一个特定的文件描述符加入到文件描述符集合中,以便后续的I/O多路复用函数(如select
、poll
等)可以监视这个文件描述符的状态变化。
基本用法
c复制代码
FD_SET(int fd, fd_set *fdset); |
- fd:需要加入到集合中的文件描述符。
- fdset:指向
fd_set
类型的指针,表示要操作的文件描述符集合。
功能
FD_SET
宏将指定的文件描述符fd
加入到fdset
指向的文件描述符集合中。这样,在后续的select
调用中,就可以监视这个集合中的文件描述符,以查看它们是否准备好进行I/O操作(如读、写或异常)。
注意事项
- 初始化:在使用
FD_SET
之前,必须首先使用FD_ZERO
宏将fd_set
集合初始化,以确保集合是空的,不包含任何文件描述符。 - 范围:
fd
的值必须在文件描述符的有效范围内,并且通常应该小于FD_SETSIZE
(一个定义在<sys/select.h>
中的常量,表示fd_set
集合能包含的文件描述符的最大数量)。不过,现代系统通常允许更大的值,但最好查阅系统文档以获取准确信息。 - 效率:由于
fd_set
集合通常是通过位图来实现的,因此添加或删除文件描述符的操作效率很高。然而,当集合中的文件描述符数量非常多时,遍历整个集合来检查哪些文件描述符已经准备好的效率可能会降低。 - 兼容性:
FD_SET
宏是POSIX标准的一部分,因此在大多数类Unix系统中都是可用的。然而,不同系统之间的实现细节可能有所不同,因此在跨平台编程时需要特别注意。
示例
#include <sys/select.h>
#include <stdio.h>
#include <unistd.h>
int main() {
fd_set readfds;
// 初始化fd_set集合
FD_ZERO(&readfds);
// 假设我们有两个文件描述符fd1和fd2
int fd1 = 0; // 例如,标准输入的文件描述符
int fd2 = 1; // 例如,标准输出的文件描述符(通常不需要监视,仅作示例)
// 将文件描述符fd1添加到readfds集合中
FD_SET(fd1, &readfds);
// ...(后续可以使用select等函数来监视readfds集合中的文件描述符)
return 0;
}
在这个示例中,我们首先使用FD_ZERO
初始化了readfds
集合,然后使用FD_SET
将文件描述符fd1
(即标准输入)添加到了集合中。接下来,我们可以使用select
函数来监视readfds
集合中的文件描述符,以查看它们是否准备好进行读操作。
FD_ISSET
FD_ISSET是一个在Linux及类Unix系统编程中广泛使用的C语言宏,用于检查指定的文件描述符(file descriptor)是否在给定的文件描述符集合(fd_set)中被设置。这个宏通常与select
、poll
等系统调用一起使用,以在I/O多路复用场景中检测文件描述符的状态。
基本用法
FD_ISSET(int fd, fd_set *fdset);
- fd:需要检查的文件描述符。
- fdset:指向
fd_set
类型的指针,表示要检查的文件描述符集合。
功能
FD_ISSET
宏检查fdset
集合中是否包含了指定的文件描述符fd
。如果fd
在集合中被设置(即,该文件描述符已经准备好进行I/O操作),则宏返回非零值(通常是1,表示真);如果fd
不在集合中,则返回零(表示假)。
使用场景
在select
调用之后,select
会更新传入的文件描述符集合,将那些准备好进行I/O操作的文件描述符的对应位设置为1,而将其他文件描述符的对应位清零。此时,可以使用FD_ISSET
来检查哪些文件描述符已经准备好,以便进行相应的处理。
注意事项
- 初始化:在使用
FD_ISSET
之前,必须确保fd_set
集合已经被正确初始化(通常使用FD_ZERO
宏),并且已经通过FD_SET
宏将需要监视的文件描述符添加到了集合中。 - 范围:
fd
的值必须在文件描述符的有效范围内,并且小于FD_SETSIZE
(一个定义在<sys/select.h>
中的常量,表示fd_set
集合能包含的文件描述符的最大数量)。然而,现代系统通常允许更大的值,但最好查阅系统文档以获取准确信息。 - 效率:由于
fd_set
集合通常是通过位图来实现的,因此FD_ISSET
操作的效率很高。然而,当集合中的文件描述符数量非常多时,遍历整个集合来检查每个文件描述符的效率可能会降低。
示例
以下是一个使用FD_ISSET
的示例代码片段,该代码片段假设已经通过select
调用了readfds
集合,并且需要检查文件描述符fd
是否在该集合中:
#include <sys/select.h>
#include <stdio.h>
#include <unistd.h>
// ...(假设readfds已经通过FD_ZERO和FD_SET初始化,并且已经调用了select)
if (FD_ISSET(fd, &readfds)) {
// fd在readfds集合中,表示该文件描述符已经准备好进行读操作
// 执行相应的读操作...
} else {
// fd不在readfds集合中,表示该文件描述符尚未准备好进行读操作
// 执行其他操作...
}
在这个示例中,FD_ISSET
用于检查文件描述符fd
是否在readfds
集合中被设置,以判断该文件描述符是否准备好进行读操作。如果FD_ISSET
返回非零值,则执行相应的读操作;否则,执行其他操作。
FD_CLR
FD_CLR是一个在Linux及类Unix系统编程中用于操作文件描述符集合(fd_set)的宏。它的主要作用是从文件描述符集合中删除一个指定的文件描述符。这个宏通常与select
、poll
等系统调用一起使用,在I/O多路复用场景中管理文件描述符集合。
基本用法
FD_CLR(int fd, fd_set *fdset);
- fd:需要从集合中删除的文件描述符。
- fdset:指向
fd_set
类型的指针,表示要操作的文件描述符集合。
功能
FD_CLR
宏将指定的文件描述符fd
从fdset
指向的文件描述符集合中删除。这样,在后续的select
调用中,就不会再监视这个被删除的文件描述符的状态变化。
使用场景
在I/O多路复用场景中,select
调用会阻塞等待直到一个或多个文件描述符准备好进行I/O操作,或者超时发生。然而,在某些情况下,程序可能需要在select
调用之前或之后从文件描述符集合中动态地添加或删除文件描述符。这时,就可以使用FD_SET
宏来添加文件描述符,使用FD_CLR
宏来删除文件描述符。
注意事项
- 初始化:在使用
FD_CLR
之前,通常需要先使用FD_ZERO
宏将fd_set
集合初始化,以确保集合是空的,不包含任何文件描述符。然而,在调用FD_CLR
时,集合已经被初始化并可能包含了一些文件描述符,因此这一步并不是FD_CLR
的直接要求,但它是管理文件描述符集合时的常见做法。 - 范围:
fd
的值必须在文件描述符的有效范围内,并且小于FD_SETSIZE
(一个定义在<sys/select.h>
中的常量,表示fd_set
集合能包含的文件描述符的最大数量)。然而,现代系统通常允许更大的值,但最好查阅系统文档以获取准确信息。 - 效率:由于
fd_set
集合通常是通过位图来实现的,因此FD_CLR
操作的效率很高。它只需要将集合中对应文件描述符的位清零即可。
示例
以下是一个使用FD_CLR
的示例代码片段,该代码片段假设已经有一个包含多个文件描述符的fd_set
集合,并且需要从该集合中删除文件描述符fd
:
#include <sys/select.h>
#include <stdio.h>
// ...(假设fdset已经通过FD_ZERO和FD_SET等宏初始化,并包含了一些文件描述符)
// 从fdset集合中删除文件描述符fd
FD_CLR(fd, &fdset);
// ...(现在fdset集合中不再包含文件描述符fd)
在这个示例中,FD_CLR
用于从fdset
集合中删除文件描述符fd
。这样,在后续的select
调用中,就不会再监视这个文件描述符的状态变化了。
应用案列
static void* _light_event_Loop(void *user) {
int kbd = open(DEV_INPUT1_NODE, O_RDONLY);
if (kbd < 0) {
printf("open %s fail!\n", DEV_INPUT1_NODE);
return NULL;
}
fd_set fdset;
FD_ZERO(&fdset);
while (1) {
FD_SET(kbd, &fdset);
if (select(FD_SETSIZE, &fdset, NULL, NULL, NULL) > 0) {
if (FD_ISSET(kbd, &fdset)) {
FD_CLR(kbd, &fdset);
struct input_event event;
if (read(kbd, &event, sizeof(event)) == sizeof(event) &&
event.type == EV_KEY &&
s_backlight_callback) {
if(event.value == 1)
{
s_backlight_callback(event.code, event.value);
}
}
}
}
}
close(kbd);
printf("_light_event_Loop...\n");
return NULL;
}