select()是如何被提醒到fd变为“就绪"的? [英] how is select() alerted to an fd becoming "ready"?

查看:114
本文介绍了select()是如何被提醒到fd变为“就绪"的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不知道为什么很难找到这个,但是我正在看一些Linux代码,我们在其中使用select()等待文件描述符来报告它已经准备好了.在选择的手册页中:

select() and pselect() allow a program to monitor multiple file descriptors,
waiting until one or more of the file descriptors become "ready" for some
class of I/O operation 

所以,太好了……我在某个描述符上调用select,给它一些超时值,然后开始等待指示显示.文件描述符(或描述符的所有者)如何报告其就绪",以使select()语句返回?

解决方案

它报告通过返回已准备就绪.

select等待通常在程序控制范围之外的事件.本质上,通过调用select,您的程序会说直到...之前,我什么都不需要做,请暂停我的过程".

您指定的条件是一系列事件,其中任何一个都会唤醒您.

例如,如果要下载某些内容,则循环将不得不等待新数据到达,如果传输被卡住或用户中断,则会发生超时,这正是select的作用. /p>

当您进行多次下载时,到达任何连接的数据都会触发程序中的活动(您需要将数据写入磁盘),因此您需要在列表中提供到select的所有下载连接的列表.用来观察读取"的文件描述符.

将数据同时上传到某个地方时,再次使用select查看连接当前是否接受数据.如果另一侧正在进行拨号,则它将仅缓慢地确认数据,因此您的本地发送缓冲区始终已满,并且任何写更多数据的尝试都将阻塞,直到缓冲区空间可用或失败为止.通过将要发送的文件描述符作为写"描述符传递到select,一旦缓冲区空间可供发送,我们就会得到通知.

一般的想法是您的程序成为事件驱动的,即它对来自公共消息循环的外部事件做出反应,而不是执行顺序操作.您告诉内核这是我要为其执行操作的一组事件",内核为您提供了一组已发生的事件.两个事件同时发生是很常见的.例如,TCP确认包含在数据包中,这可以使相同的fd可读(数据可用)和可写(已确认的数据已从发送缓冲区中删除),因此您应该准备处理所有事件在再次调用select之前.

更好的一点是,select基本上可以向您保证,如果不保证调用本身,对readwrite的一次调用将不会阻塞.例如,如果有一个字节的缓冲区空间可用,您可以尝试写入10个字节,内核将返回并说我已写入1个字节",因此您也应该准备处理这种情况.一种典型的方法是使用一个缓冲区要写入此fd的数据",只要它是非空的,就将fd添加到写入集中,并通过尝试写入所有数据来处理"writeable"事件当前缓冲区中的数据.如果此后缓冲区为空,则可以,否则,请再次等待可写".

很少使用例外"集-它用于具有带外数据的协议,在这些协议中可能会阻止数据传输,而其他数据则需要通过.如果您的程序当前不能接受来自可读"文件描述符的数据(例如,您正在下载并且磁盘已满),则您不想在可读"集中包含描述符,因为您无法处理该事件如果再次调用select,将立即返回.如果接收方将fd包括在例外"集中,并且发送方要求其IP堆栈发送包含紧急"数据的数据包,则接收方会被唤醒,并可以决定丢弃未处理的数据并与发送方重新同步. telnet协议将其用于例如Ctrl-C处理.除非您正在设计需要这种功能的协议,否则可以轻松地将其忽略而不会造成损害.

强制性代码示例:

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

#include <unistd.h>

#include <stdbool.h>

static inline int max(int lhs, int rhs) {
    if(lhs > rhs)
        return lhs;
    else
        return rhs;
}

void copy(int from, int to) {
    char buffer[10];
    int readp = 0;
    int writep = 0;
    bool eof = false;
    for(;;) {
        fd_set readfds, writefds;
        FD_ZERO(&readfds);
        FD_ZERO(&writefds);

        int ravail, wavail;
        if(readp < writep) {
            ravail = writep - readp - 1;
            wavail = sizeof buffer - writep;
        }
        else {
            ravail = sizeof buffer - readp;
            wavail = readp - writep;
        }

        if(!eof && ravail)
            FD_SET(from, &readfds);
        if(wavail)
            FD_SET(to, &writefds);
        else if(eof)
            break;
        int rc = select(max(from,to)+1, &readfds, &writefds, NULL, NULL);
        if(rc == -1)
            break;
        if(FD_ISSET(from, &readfds))
        {
            ssize_t nread = read(from, &buffer[readp], ravail);
            if(nread < 1)
                eof = true;
            readp = readp + nread;
        }
        if(FD_ISSET(to, &writefds))
        {
            ssize_t nwritten = write(to, &buffer[writep], wavail);
            if(nwritten < 1)
                break;
            writep = writep + nwritten;
        }
        if(readp == sizeof buffer && writep != 0)
            readp = 0;
        if(writep == sizeof buffer)
            writep = 0;
    }
}

我们尝试读取是否有可用的缓冲区空间,并且读取侧没有文件尾或错误,并且如果缓冲区中有数据,则尝试写入.如果到达文件末尾并且缓冲区为空,那么我们就完成了.

该代码的行为显然不是最佳选择(它是示例代码),但是您应该能够看到,内核执行的操作少于我们要求的读写能力,在这种情况下,我们只需要回头再读一次即可.说只要你准备好了",我们就不会在不询问它是否会阻塞的情况下进行读写.

I don't know why I'm having a hard time finding this, but I'm looking at some linux code where we're using select() waiting on a file descriptor to report it's ready. From the man page of select:

select() and pselect() allow a program to monitor multiple file descriptors,
waiting until one or more of the file descriptors become "ready" for some
class of I/O operation 

So, that's great... I call select on some descriptor, give it some time out value and start to wait for the indication to go. How does the file descriptor (or owner of the descriptor) report that it's "ready" such that the select() statement returns?

解决方案

It reports that it's ready by returning.

select waits for events that are typically outside your program's control. In essence, by calling select, your program says "I have nothing to do until ..., please suspend my process".

The condition you specify is a set of events, any of which will wake you up.

For example, if you are downloading something, your loop would have to wait on new data to arrive, a timeout to occur if the transfer is stuck, or the user to interrupt, which is precisely what select does.

When you have multiple downloads, data arriving on any of the connections triggers activity in your program (you need to write the data to disk), so you'd give a list of all download connections to select in the list of file descriptors to watch for "read".

When you upload data to somewhere at the same time, you again use select to see whether the connection currently accepts data. If the other side is on dialup, it will acknowledge data only slowly, so your local send buffer is always full, and any attempt to write more data would block until buffer space is available, or fail. By passing the file descriptor we are sending to to select as a "write" descriptor, we get notified as soon as buffer space is available for sending.

The general idea is that your program becomes event-driven, i.e. it reacts to external events from a common message loop rather than performing sequential operations. You tell the kernel "this is the set of events for which I want to do something", and the kernel gives you a set of events that have occured. It is fairly common for two events occuring simultaneously; for example, a TCP acknowledge was included in a data packet, this can make the same fd both readable (data is available) and writeable (acknowledged data has been removed from send buffer), so you should be prepared to handle all of the events before calling select again.

One of the finer points is that select basically gives you a promise that one invocation of read or write will not block, without making any guarantee about the call itself. For example, if one byte of buffer space is available, you can attempt to write 10 bytes, and the kernel will come back and say "I have written 1 byte", so you should be prepared to handle this case as well. A typical approach is to have a buffer "data to be written to this fd", and as long as it is non-empty, the fd is added to the write set, and the "writeable" event is handled by attempting to write all the data currently in the buffer. If the buffer is empty afterwards, fine, if not, just wait on "writeable" again.

The "exceptional" set is seldom used -- it is used for protocols that have out-of-band data where it is possible for the data transfer to block, while other data needs to go through. If your program cannot currently accept data from a "readable" file descriptor (for example, you are downloading, and the disk is full), you do not want to include the descriptor in the "readable" set, because you cannot handle the event and select would immediately return if invoked again. If the receiver includes the fd in the "exceptional" set, and the sender asks its IP stack to send a packet with "urgent" data, the receiver is then woken up, and can decide to discard the unhandled data and resynchronize with the sender. The telnet protocol uses this, for example, for Ctrl-C handling. Unless you are designing a protocol that requires such a feature, you can easily leave this out with no harm.

Obligatory code example:

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

#include <unistd.h>

#include <stdbool.h>

static inline int max(int lhs, int rhs) {
    if(lhs > rhs)
        return lhs;
    else
        return rhs;
}

void copy(int from, int to) {
    char buffer[10];
    int readp = 0;
    int writep = 0;
    bool eof = false;
    for(;;) {
        fd_set readfds, writefds;
        FD_ZERO(&readfds);
        FD_ZERO(&writefds);

        int ravail, wavail;
        if(readp < writep) {
            ravail = writep - readp - 1;
            wavail = sizeof buffer - writep;
        }
        else {
            ravail = sizeof buffer - readp;
            wavail = readp - writep;
        }

        if(!eof && ravail)
            FD_SET(from, &readfds);
        if(wavail)
            FD_SET(to, &writefds);
        else if(eof)
            break;
        int rc = select(max(from,to)+1, &readfds, &writefds, NULL, NULL);
        if(rc == -1)
            break;
        if(FD_ISSET(from, &readfds))
        {
            ssize_t nread = read(from, &buffer[readp], ravail);
            if(nread < 1)
                eof = true;
            readp = readp + nread;
        }
        if(FD_ISSET(to, &writefds))
        {
            ssize_t nwritten = write(to, &buffer[writep], wavail);
            if(nwritten < 1)
                break;
            writep = writep + nwritten;
        }
        if(readp == sizeof buffer && writep != 0)
            readp = 0;
        if(writep == sizeof buffer)
            writep = 0;
    }
}

We attempt to read if we have buffer space available and there was no end-of-file or error on the read side, and we attempt to write if we have data in the buffer; if end-of-file is reached and the buffer is empty, then we are done.

This code will behave clearly suboptimal (it's example code), but you should be able to see that it is acceptable for the kernel to do less than we asked for both on reads and writes, in which case we just go back and say "whenever you're ready", and that we never read or write without asking whether it will block.

这篇关于select()是如何被提醒到fd变为“就绪"的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆