Получу ли я уведомление от epoll, когда fd закрыт?

В настоящее время я создаю что-то, что использует epoll. Это работает довольно хорошо, но было бы хорошо иметь уведомление, когда дескриптор файла удаляется из epoll, когда базовый fd закрывается.

Есть ли способ получить уведомление от epoll, как только fd закрывается?


person Nidhoegger    schedule 26.03.2017    source источник
comment
Что вы хотите сделать с таким уведомлением?   -  person n. 1.8e9-where's-my-share m.    schedule 27.03.2017
comment
Это относится к вопросу? Я обернул epoll в класс C++, и у меня есть список, который сопоставляет fd с объектом. Обычно для удаления fd следует вызывать delete, но я также хочу поймать ошибку при закрытии и очистить сопоставленный объект списка...   -  person Nidhoegger    schedule 27.03.2017
comment
Если вы хотите проверить наличие ошибок, вы можете сделать это вручную. Просмотрите дескрипторы и вызовите epoll_ctl(EPOLL_CTL_MOD) для каждого. Если вы получаете сообщение об ошибке, вы знаете, что он был закрыт. Но лучше инкапсулируйте и спрячьте fd и не позволяйте ему закрываться по прихоти.   -  person n. 1.8e9-where's-my-share m.    schedule 27.03.2017
comment
Теоретически он не должен быть закрыт, пока он находится в epoll. Но напр. сокет можно закрыть удаленно   -  person Nidhoegger    schedule 27.03.2017
comment
Нет, его можно выключить удаленно, но не закрыть. Он закрывается только при вызове close.   -  person n. 1.8e9-where's-my-share m.    schedule 27.03.2017
comment
ах хорошо, спасибо за это различие. тогда у меня вообще нет проблем :). Благодарю вас!   -  person Nidhoegger    schedule 27.03.2017
comment
Вы не только не получите уведомление, но и ваша посылка неверна: она не может быть удалена из epoll при закрытии. Вы можете продолжать получать уведомления о закрытом сокете, и у вас нет возможности узнать об этом.   -  person lvella    schedule 19.09.2019


Ответы (3)


Нет. Вот программа Zig для демонстрации:

const std = @import("std");

pub fn main() !void {
    const epollfd = blk: {
        const rc = std.os.linux.epoll_create1(std.os.linux.EPOLL_CLOEXEC);
        const err = std.os.linux.getErrno(rc);
        if (err != 0) return error.EpollCreateFailed;
        break :blk @intCast(i32, rc);
    };
    defer std.os.close(epollfd);

    var pipe: [2]i32 = undefined;
    {
        const rc = std.os.linux.pipe2(&pipe, std.os.linux.O_CLOEXEC);
        const err = std.os.linux.getErrno(rc);
        if (err != 0) return error.PipeCreateFailed;
    }

    var ev: std.os.linux.epoll_event = undefined;

    ev.events = std.os.linux.EPOLLIN | std.os.linux.EPOLLET;
    ev.data.fd = pipe[0];
    {
        const rc = std.os.linux.epoll_ctl(epollfd, std.os.linux.EPOLL_CTL_ADD, pipe[0], &ev);
        const err = std.os.linux.getErrno(rc);
        if (err != 0) return error.EpollCtlFailed;
    }

    const thread = try std.os.spawnThread(pipe[0], threadStart);

    var events: [10]std.os.linux.epoll_event = undefined;
    const nfds = blk: {
        std.debug.warn("epoll wait\n");
        const rc = std.os.linux.epoll_wait(epollfd, &events, @intCast(u32, events.len), -1);
        std.debug.warn("epoll wakeup\n");
        const err = std.os.linux.getErrno(rc);
        if (err != 0) return error.EpollWaitFailed;
        break :blk rc;
    };
    if (nfds != 1) return error.NotExactly1FileDescriptor;
    std.debug.assert(events[0].data.fd == pipe[0]);
    thread.wait();
}

fn threadStart(fd: i32) void {
    std.os.time.sleep(1, 0);
    std.debug.warn("close fd\n");
    std.os.close(fd);
}

Он выводит:

epoll wait
close fd
^C

(Мне пришлось убить его с помощью Ctrl+C, потому что он так и не проснулся.)

Вот вывод strace:

execve("./test", ["./test"], 0x7fff311ff208 /* 122 vars */) = 0
epoll_create1(EPOLL_CLOEXEC)            = 3
pipe2([4, 5], O_CLOEXEC)                = 0
epoll_ctl(3, EPOLL_CTL_ADD, 4, {EPOLLIN|EPOLLET, {u32=4, u64=4}}) = 0
mmap(NULL, 8388608, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0) = 0x7fd082ed9000
clone(child_stack=0x7fd0836d8fd8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID|0x400000, parent_tidptr=0x7fd0836d8fe4, child_tidptr=0x7fd0836d8fe4) = 13547
strace: Process 13547 attached
[pid 13546] write(2, "epoll wait\n", 11epoll wait
<unfinished ...>
[pid 13547] nanosleep({tv_sec=1, tv_nsec=0},  <unfinished ...>
[pid 13546] <... write resumed> )       = 11
[pid 13546] epoll_wait(3,  <unfinished ...>
[pid 13547] <... nanosleep resumed> 0x7fd0836d8f20) = 0
[pid 13547] write(2, "close fd\n", 9close fd
)   = 9
[pid 13547] close(4)                    = 0
[pid 13547] exit(0)                     = ?
[pid 13547] +++ exited with 0 +++
^Cstrace: Process 13546 detached
person andrewrk    schedule 26.07.2018
comment
Проголосовал, потому что вы единственный, кто хотя бы понял вопрос: с. Я сделал тот же тест на C++ и получил те же результаты: close() не вызывает пробуждение от epoll_wait(). Это почти тривиальное ожидаемое поведение, потому что общий случай использования - это когда data.fd устанавливается в (теперь закрытый) fd, или в противном случае вполне нормально, что после удаления из списка интереса все, на что указывает data.ptr, удаляется, поэтому вы действительно не нужно «событие» для этого объекта. Однако, и именно поэтому меня интересует этот вопрос / ответ, похоже, есть условие гонки: если событие произойдет - person Carlo Wood; 10.07.2019
comment
и epoll_wait() возвращает - и ТОГДА немедленно другой поток закрывает этот fd - у вас есть точно такая же проблема, как если бы close() вызвал пробуждение: либо data.fd теперь недействителен (или даже был повторно используется и указывает на новое устройство) или data.ptr был удален (моя проблема). Кажется, что единственный способ решить эту проблему — добавить новый флаг для epoll_ctl, который позволяет получать уведомления о событии, которое запрашивает удаление data.ptr, возможно, инициированное close(2). - person Carlo Wood; 10.07.2019

Вам нужно следить за тем, когда epoll_wait() возвращает -1, а затем проверять значение errno, чтобы убедиться, что оно соответствует EBADF.

Подробнее см. man epoll_wait(), man errno.

person rakib_    schedule 27.03.2017
comment
Спасибо за Ваш ответ. Согласно справочной странице: EBADF epfd is not a valid file descriptor.. Таким образом, это применимо только к файловому дескриптору самого объекта epoll, а не к файловым каталогам, которые содержит объект epoll. - person Nidhoegger; 27.03.2017
comment
Когда файл закрыт; дескриптор файла будет недействительным. - person rakib_; 27.03.2017
comment
Закрытые фд удаляются из набора epoll автоматически, так что ошибки не будет. - person n. 1.8e9-where's-my-share m.; 27.03.2017
comment
в яблочко. Поэтому я думаю, что единственный способ - попытаться добавить каждый fd в список и посмотреть, выдаст ли EPOLL_CTL_ADD сообщение об ошибке EEXISTS. Что некрасиво... - person Nidhoegger; 27.03.2017
comment
Этот пост не является ответом на актуальный вопрос. - person Carlo Wood; 10.07.2019

Нет. Как только файловый дескриптор закрывается, он больше не доступен ни для чего, кроме как для использования ядром в новом файловом дескрипторе. Как только вы не получите его снова в результате нового системного вызова open(2), create(2), socket(2) и т. д., его нельзя будет использовать в качестве параметра для любого системного вызова (включая select(2) и epoll(2))

Ядро освободило запись файлового дескриптора в структуре вашего процесса, поэтому ее нельзя использовать.

Извините, я сказал:

Единственный способ получить что-то от epoll(2) в случае его закрытия - это если у вас есть два потока и вы close(2) дескриптор файла в одном потоке, пока вы ожидаете его в другом потоке. На этот раз вы получите немедленный возврат из системного вызова epoll(2), возможно, с какой-то ошибкой (я не проверял это), но я думаю, что это не ваш сценарий.

Как я тогда сказал, я его не проверял, но сейчас могу сказать, что системные вызовы в процессе не прерываются, а оставляются до конца. В любом случае, файловый дескриптор больше недействителен ни в одном потоке, который будет использоваться для нового системного вызова. Код в ПРИМЕЧАНИИ ниже показывает, как это происходит. Но вывод таков, что после close(2) вы больше не сможете использовать в процессе дескриптор файла, переданный системному вызову, как я уже говорил. (конечно, вы можете, если вы получите то же значение файлового дескриптора в результате нового системного вызова open(2), socket(2) и т. д.)

ПРИМЕЧАНИЕ

только что протестировано со следующим кодом: если вы close(2) дескриптор файла и другие потоки заблокированы в системных вызовах, связанных с этим дескриптором файла, системные вызовы не прерываются, но дескриптор файла больше недействителен. После завершения этих системных вызовов файловый дескриптор становится непригодным для использования ни в потоке, который его close(2)d, ни в других потоках.

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

char buffer[100];

void *f(void* arg)
{
    int res;
    printf("f: waiting on read()\n");
    res = read(0, buffer, sizeof buffer);
    printf("f: read %d bytes\n", res);
    if (res < 0) {
            printf("this was an error: %s\n", strerror(errno));
    } else {
            printf("f: read: %.*s", res, buffer);
    }
    printf("f: waiting on read()\n");
    res = read(0, buffer, sizeof buffer);
    printf("f: read %d bytes\n", res);
    if (res < 0) {
            printf("f: this was an error: %s\n", strerror(errno));
    } else {
            printf("f: read: %.*s", res, buffer);
    }
    pthread_exit(NULL);
}

int main()
{
    pthread_t t;
    int res = pthread_create(&t, NULL, f, NULL);
    printf("main: waiting 10 seconds\n");
    sleep(10);
    printf("main: closing stdin\n");
    close(0);
    printf("main: waiting another 10s.\n");
    sleep(10);
    printf("main: waiting for thread.\n");
    pthread_join(t, NULL);
    printf("main: reading from stdin\n");
    res = read(0, buffer, sizeof buffer);
    printf("main: read %d bytes\n", res);
    if (res < 0) {
            printf("main: this was an error: %s\n", strerror(errno));
    } else {
            printf("main: read: %.*s", res, buffer);
    }
    printf("main: exiting.\n");
}

Этот код приводит к следующей последовательности:

$ a.out
main: waiting 10 seconds
f: waiting on read()
main: closing stdin               <--- now the fd is closed
main: waiting another 10s.
lorem ipsum                       <--- input
f: read 12 bytes
f: read: lorem ipsum
f: waiting on read()
f: read -1 bytes                  <--- file descriptor is not valid in the second read(2) call.
f: this was an error: Bad file descriptor
main: waiting for thread.         <--- joining thread f
main: reading from stdin
main: read -1 bytes               <--- invalid also for main thread.
main: this was an error: Bad file descriptor
main: exiting.
person Luis Colorado    schedule 28.03.2017
comment
голосование против без объяснения причин не является ни вежливым, ни поучительным. Пожалуйста, оставьте заметку по причине отрицательного голоса. Будьте позитивны, даже когда голосуете против :) - person Luis Colorado; 30.07.2018
comment
Я тоже проголосовал за это, и хорошо, потому что это неправильно. Если вы close(2) используете fd, это не приведет к возврату epoll_wait(2) ни в одном потоке. Кроме того, первая часть кажется не связанной с тем, что спросил OP: можно ли заставить epoll_wait(2) возвращаться с «закрытым событием», когда другой поток закрывает fd. - person Carlo Wood; 10.07.2019
comment
@CarloWood, извините, но я не говорю, что это привело к возврату какого-либо системного вызова в курсе. Я сказал, что вероятно (и что я этого не проверял) любые активные системные вызовы могут быть прерваны на close(2) (фактически это делает драйвер tty в результате аннулирования, выполненного с ним на последнее закрытие tty, которое возвращает любой текущий системный вызов, но в Linux это отличается от Unix) В любом случае, я обновил свой ответ, чтобы отразить то, что на самом деле происходит с примером. - person Luis Colorado; 10.07.2019