IO——select 函数

如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。

  • poll、epoll 和 select 可以用于处理轮询
  • 应用程序通过 select、epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。
  • 当应用程序调用 select、epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数

先来看一下应用程序中使用的 select 这个函数,poll 和 epoll 的使用待补充

函数原型及参数解析

函数原型

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

参数解析

nfds

  • nfds 表示需要监控的文件描述符的最大值加1。这个值通常设为所有文件描述符中的最大值加1,以确保select能够正确地监控所有需要的文件描述符。

readfds/writefds/exceptfds

fd_set 结构体原型

// 定义类型别名 __fd_mask,本质是 long int
typedef long int __fd_mask;/* fd_set 记录要监听的fd集合,及其对应状态 */
typedef struct {
    // fds_bits是long类型数组,长度为 1024/32 = 32
    // 共1024个bit位,每个bit位代表一个fd,0代表未就绪,1代表就绪
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
    // ...
} fd_set;

所以 select 函数能够监视的文件描述符数量有最大的限制,一般是 1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率。

  • readfds 表示要监控的读的文件描述符集合,也就是监视这些文件是否可以读取,只要这些集合里面有一个文件可以读取,那么 select 就会返回一个大于 0 的值表示文件可以读取。如果没有文件可以读取,那么就会根据 timeout 参数来判断是否超时,可以将 readfds 设置为 NULL,表示不关心任何文件的读变化
  • writefds 表示要监控的写的文件描述符集合,和 readfds 类似,用于监视这些文件是否可以进行写操作。
  • exceptfds 表示要监控的异常条件的文件描述符集合,和 readfds 类似,用于监视这些文件的异常。

它们都是由 fd_set 类型表示的位图结构。可以使用以下四个宏来操作这些集合:

  • FD_SET(fd, &set): 将文件描述符 fd 添加到 se t集合中,用于将 fd_set 变量的某个 bit 置 1。
  • FD_CLR(fd, &set): 从 set 集合中删除文件描述符 fd,用于将 fd_set 变量的某个 bit 清零。
  • FD_ISSET(fd, &set): 检查 fd 是否在 set 集合中。
  • FD_ZERO(&set): 清空 set 集合,用于将 fd_set 变量的所有 bit 都清零。

timeout

  • timeout 参数是一个 timeval 结构指针,用于设置 select 函数的超时时间。
    • 当 timeout 为 NULL 时,select 将无限期地等待,直到有文件描述符准备好。
    • 当 timeout 设置为 0 时,select 将立即返回。
    • 当 timeout 设置为非零值时,select 将等待指定的时间,直到有文件描述符准备好或超时。
struct timeval {
    long tv_sec;   /* seconds */
    long tv_usec;  /* microseconds */
};

返回值

select函数的返回值表示以下三种情况:

  • 返回值大于0:表示有准备好的文件描述符,即已经发生的I/O事件数量。
  • 返回值等于0:表示超时,即在指定的时间内没有任何I/O事件发生。
  • 返回值小于0:表示发生错误。在这种情况下,可以使用perror或strerror函数来获取错误信息。

在调用 select 函数后,可以通过检查 readfds,writefds 和 exceptfds 集合的状态,以确定哪些文件描述符准备好进行 I/O 操作。然后,程序可以根据文件描述符的状态来执行相应的读、写或异常处理操作。

示例程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>

int main(void)
{
    fd_set readfds;
    struct timeval timeout;
    int ret, fd_max;

    /* 创建两个管道 */
    int pipefds1[2];
    int pipefds2[2];

    pipe(pipefds1);
    pipe(pipefds2); /* 向管道写入数据 */
    write(pipefds1[1], "Hello", 5);
    write(pipefds2[1], "World", 5);

    while (1)
    {
        FD_ZERO(&readfds); /* 清除 readfds */
        FD_SET(pipefds1[0], &readfds); /* 将 pipe0 读 fd 添加到 readfds 集合中 */
        FD_SET(pipefds2[0], &readfds); /* 将 pipe1 读 fd 添加到 readfds 集合中 */

        /* 设置最大文件描述符 */
        fd_max = (pipefds1[0] > pipefds2[0]) ? pipefds1[0] : pipefds2[0];

        /*  设置超时时间 */
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        ret = select(fd_max + 1, &readfds, NULL, NULL, &timeout);
        if (ret == -1)
        {
            perror("select"); /* 错误 */
            exit(EXIT_FAILURE);
        }
        else if (ret == 0)
        {
            printf("Timeout!\n"); /* 超时 */
            break;
        }
        else /* 可以读取数据 */
        {
            if (FD_ISSET(pipefds1[0], &readfds)) /* 判断是否为 pipe0 读 fd */
            {
                char buf[6];
                read(pipefds1[0], buf, 5);
                buf[5] = '\0';
                printf("Data from pipe1: %s\n", buf);
            }
            if (FD_ISSET(pipefds2[0], &readfds)) /* 判断是否为 pipe1 读 fd */
            {
                char buf[6];
                read(pipefds2[0], buf, 5);
                buf[5] = '\0';
                printf("Data from pipe2: %s\n", buf);
            }
            break;
        }
    }

    close(pipefds1[0]);
    close(pipefds1[1]);
    close(pipefds2[0]);
    close(pipefds2[1]);

    return 0;
}

程序运行结果

Data from pipe1: Hello
Data from pipe2: World

缺点

  • 一开始需要将整个fd_set从用户空间拷贝到内核空间,在select结束之后还要再次拷贝回用户空间
  • 文件描述符数量限制:select函数所能处理的文件描述符数量受限于FD_SETSIZE,这可能导致无法处理大量连接的问题。
  • 效率问题:当文件描述符数量增加时,select函数的效率会降低,因为它需要遍历所有文件描述符以检查状态。

参考

  • https://www.xjx100.cn/news/212725.html?action=onClick
  • https://blog.csdn.net/weixin_44471948/article/details/120846877
  • https://juejin.cn/post/7129070726249709599#heading-8
  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`select` 函数是一种 I/O 多路复用的机制,用于同时监听多个文件描述符的状态变化。它可以使用单个系统调用同时监视多个文件描述符,并在有一个或多个文件描述符就绪时通知应用程序。 `select` 函数的原型如下: ```c #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` 参数说明: - `nfds`:待监视的最大文件描述符加 1。 - `readfds`:可读文件描述符集合。 - `writefds`:可写文件描述符集合。 - `exceptfds`:异常条件文件描述符集合。 - `timeout`:超时时间,如果为 `NULL` 则为阻塞模式,即一直等待直到有文件描述符就绪;如果为零时间(`tv_sec` 和 `tv_usec` 均为 0),则为非阻塞模式,即立即返回;否则为指定超时时间。 `select` 函数的返回值表示就绪文件描述符的数量,如果返回值为 0,则表示超时;如果返回值为 -1,则表示出错。 使用 `select` 函数的一般流程如下: 1. 初始化需要监视的文件描述符集合。 2. 调用 `select` 函数等待文件描述符就绪。 3. 检查返回值确定哪些文件描述符已经就绪。 4. 处理就绪的文件描述符。 5. 重复上述步骤。 需要注意的是,`select` 函数有一些限制,比如只能监视的文件描述符数量有限,一般为 1024 或更小。此外,在某些平台上,使用 `select` 函数可能会有性能上的限制,可以考虑使用更高效的机制,如 `poll` 或 `epoll`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值