Как прикрепить слушатель к файлам sysfs?

Вопрос или проблема

Как следить за изменениями файлов sysfs (например, /sys/class/net/eth0/statistics/operstate) и выполнять команду при изменении содержимого?

  • inotify не работает с sysfs
  • Я не хочу использовать опрос. Я хочу установить прослушиватель с обратным вызовом один раз

Я не читал исходный код, который заполняет operstate, но, как правило, чтение файла в sysfs выполняет некоторый код на стороне ядра, который возвращает байты, которые вы читаете. Таким образом, без вашего чтения operstate у него нет “состояния”. Значение нигде не хранится.

Как следить за изменениями файла sysfs

Так как это не настоящие файлы, концепция “изменения” не существует.

Вероятно, есть лучший способ достичь того, что вы хотите! netlink был разработан специально для задачи мониторинга состояния сетевого подключения; он легко интерфейсируется. Например, этот минимально измененный образец кода из man 7 netlink может уже решить вашу проблему:

       struct sockaddr_nl sa;

       memset(&sa, 0, sizeof(sa));
       sa.nl_family = AF_NETLINK;
       // Уведомления об изменении состояния соединения:
       sa.nl_groups = RTMGRP_LINK;

       fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
       bind(fd, (struct sockaddr *) &sa, sizeof(sa));

В общем, если это не связано с подключением на уровне Ethernet, а, скажем, с подключением к какой-либо IP сети (или интернету), то на современной системе вам стоит использовать systemd/NetworkManager.

Как уже хорошо объяснил Мюллер, вы не можете мониторить файлы sysfs, так как они являются частью виртуальной файловой системы и не ведут себя как обычные файлы.

Я в конечном итоге погрузился в написание кода на C, и, хотя он абсолютно не тестировался и не выглядит хорошо, вот фрагмент из этого примера, как более широкий пример из netlink и rnetlink:

./realtimnetlink
Мониторинг
RTM NEWLINK enp12s0 DOWN
RTM NEWLINK enp12s0 UP
RTM NEWLINK eth10 DOWN
RTM NEWADDR eth10
RTM NEWLINK eth10 UP

Скомпилируйте с помощью gcc -o foo foo.c

#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <sys/socket.h>
#include <linux/rtnetlink.h>
#include <net/if.h>

#define E_PRINT(f_, ...) fprintf(stderr, ("ERROR NetLink: %s" f_ "\n"), ##__VA_ARGS__)

int open_netlink()
{
    int soc; // fd
    struct sockaddr_nl sa;

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;
    sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
    soc = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (soc < 0) {
        perror("INIT socket: ");
        return -1;
    }
    if (bind(soc, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
        perror("INIT bind_socket:");
        return -1;
    }
    return soc;
}
int event_read(
    int sockint,
    int (*msg_handler)(struct nlmsghdr *)
) {
    int status;
    int ret = 0;
    char buf[4096];
    struct iovec iov = { buf, sizeof buf };
    struct sockaddr_nl snl;
    struct msghdr msg = {
        (void*)&snl,
        sizeof snl,
        &iov, 1, NULL, 0, 0
    };
    struct nlmsghdr *hdr;

    status = recvmsg(sockint, &msg, 0);

    if (status < 0) {
        /* Socket non-blocking so bail out once we have read everything */
        if (errno == EWOULDBLOCK || errno == EAGAIN)
            return ret;
        /* Anything else is an error */
        E_PRINT("recvmsg: %d", "EVT_READ", status);
        perror("ERROR read_netlink: ");
        return status;
    } else if (status == 0) {
        E_PRINT("recvmsg: EOF", "EVT_READ");
    }

    /* Мы должны обработать более одного сообщения за 'recvmsg' */
    for (
        hdr = (struct nlmsghdr *) buf;
        NLMSG_OK (hdr, (unsigned int)status);
        hdr = NLMSG_NEXT (hdr, status)
    ) {
        /* Завершить чтение */
        if (hdr->nlmsg_type == NLMSG_DONE)
            return ret;

        /* Сообщение является ошибкой */
        if (hdr->nlmsg_type == NLMSG_ERROR) {
            E_PRINT("Decode to be done", "EVT_READ");
            return -1;
        }
        /* Вызов обработчика сообщений */
        if (msg_handler) {
            ret = (*msg_handler)(hdr);
            if (ret < 0) {
                E_PRINT("msg_handler: %d", "EVT_READ", ret);
                return ret;
            }
        } else {
            E_PRINT("NULL message handler", "EVT_READ");
            return -1;
        }
    }
    return ret;
}
static int msg_handler(struct nlmsghdr *msg)
{
    struct ifinfomsg *ifi = NLMSG_DATA(msg);
    struct ifaddrmsg *ifa = NLMSG_DATA(msg);
    char ifname[1024];

    switch (msg->nlmsg_type) {
    case RTM_NEWADDR:
        if_indextoname(ifa->ifa_index, ifname);
        printf("RTM NEWADDR %s\n", ifname);
        break;
    case RTM_DELADDR:
        if_indextoname(ifa->ifa_index, ifname);
        printf("RTM DELADDR %s\n", ifname);
        break;
    case RTM_NEWLINK:
        if_indextoname(ifi->ifi_index, ifname);
        printf("RTM NEWLINK %s %s\n",
            ifname,
            (ifi->ifi_flags & IFF_UP) ? "UP" : "DOWN"
        );
        break;
    case RTM_DELLINK:
        if_indextoname(ifi->ifi_index, ifname);
        printf("RTM DELLINK %s\n", ifname);
        break;
    default:
        fprintf(stderr,
            "RTM UNKNOWN nlmsg_type %d\n",
            msg->nlmsg_type
        );
        break;
    }
    return 0;
}

int main(void)
{
    int nls = open_netlink();
    if (nls < 0) {
        E_PRINT("Open Error!", "INIT");
        return 1;
    }
    fprintf(stderr, "Мониторинг\n");
    while (1)
        event_read(nls, msg_handler);
    return 0;
}

Существует механизм для драйверов ядра Linux, позволяющий атрибутам sysfs вызывать события изменения при модификации базовых данных. Изначально это задумывалось как способ сделать атрибуты sysfs “опросными” с помощью функций poll() или select(). В драйвере ядра можно вызвать функцию sysfs_notify() или sysfs_notify_dirent(). В пользовательском приложении можно открыть атрибут sysfs и вызвать poll() на файловом дескрипторе с установленным событием POLLPRI.

Пример пользовательского приложения для опроса атрибута sysfs: https://github.com/cmcqueen/sysfs-poll

Не многие атрибуты sysfs опросные таким образом, но некоторые сделаны такими. (Сейчас я не могу найти хороший общий пример опросного атрибута sysfs.)

Этот механизм ядра также, похоже, работает для вызова событий inotify MODIFY. Я тестировал его с ядром версии 5.4.106 и настраиваемым драйвером. Я смог выполнить

inotifywait -m /sys/class/mydriver/.../myattribute

Я получал событие MODIFY каждый раз, когда данные для атрибута изменялись (поскольку я написал свой драйвер для вызова sysfs_notify(), когда базовые данные изменялись).

Ответ или решение

Чтобы ответить на вопрос о мониторинге изменений в файлах системы sysfs и выполнении команды при изменении их содержимого, необходимо рассмотреть несколько важных аспектов. Давайте углубимся в особенности работы с sysfs, ограничения и более подходящие альтернативные подходы.

Теория

sysfs — это виртуальная файловая система в Linux, которая отображает структуру и иерархию ядра операционной системы, предоставляя доступ к информации о устройствах и драйверах. Из-за своей природы sysfs не хранит данные в традиционной форме, как на жестком диске, а генерирует их динамически. Это означает, что концепция "изменения" файла в sysfs не применима, так как данные создаются по запросу. Инструменты, такие как inotify, которые работают с реальными изменениями в файловой системе, на sysfs не действуют.

Пример

Рассмотрим ситуацию, когда нужно отслеживать изменения в состоянии сетевого интерфейса, например, /sys/class/net/eth0/statistics/operstate. Как уже отмечено, inotify для sysfs не подходит из-за его виртуальной природы. Чтобы решить эту проблему, мы можем использовать netlink, который предназначен для уведомлений о состоянии сети. При помощи netlink вы можете следить за изменениями состояния сетевых интерфейсов, таких как UP или DOWN.

Применение

Для мониторинга состояния сетевых интерфейсов через netlink необходимо настроить сокет и установить подписку на нужные группы. Пример кода на языке C представлен ниже. Этот код устанавливает слушатель на изменения состояния сетевых интерфейсов.

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>
#include <net/if.h>

// Функция открытия сокета netlink
int open_netlink() {
    int sockfd;
    struct sockaddr_nl sa;

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;
    sa.nl_groups = RTMGRP_LINK;

    sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sockfd < 0) {
        perror("Ошибка при создании сокета");
        return -1;
    }
    if (bind(sockfd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
        perror("Ошибка при привязке сокета");
        return -1;
    }
    return sockfd;
}

// Функция обработки сообщений о событиях
static int msg_handler(struct nlmsghdr *msg) {
    struct ifinfomsg *ifi = NLMSG_DATA(msg);
    char ifname[IFNAMSIZ];

    if_indextoname(ifi->ifi_index, ifname);

    if (msg->nlmsg_type == RTM_NEWLINK) {
        printf("Изменение состояния интерфейса %s: %s\n", ifname, (ifi->ifi_flags & IFF_UP) ? "UP" : "DOWN");
    }
    return 0;
}

// Чтение событий и вызов обработчика событий
int event_read(int sockfd, int (*handler)(struct nlmsghdr *)) {
    char buffer[4096];
    struct iovec iov = { buffer, sizeof buffer };
    struct sockaddr_nl snl;
    struct msghdr msg = {
        .msg_name = &snl,
        .msg_namelen = sizeof(snl),
        .msg_iov = &iov,
        .msg_iovlen = 1
    };
    struct nlmsghdr *hdr;

    int status = recvmsg(sockfd, &msg, 0);
    if (status < 0) {
        perror("Ошибка при получении сообщения");
        return -1;
    }

    for (hdr = (struct nlmsghdr *)buffer; NLMSG_OK(hdr, (unsigned int)status); hdr = NLMSG_NEXT(hdr, status)) {
        if (handler) {
            handler(hdr);
        }
    }
    return 0;
}

int main() {
    int sockfd = open_netlink();
    if (sockfd < 0) {
        fprintf(stderr, "Не удалось открыть сокет\n");
        return 1;
    }

    printf("Слушаю изменения состояния сетевых интерфейсов...\n");
    while (1) {
        event_read(sockfd, msg_handler);
    }

    return 0;
}

Заключение

Хотя sysfs не предоставляет механизма для мониторинга изменений в стиле традиционных файловых систем, использование netlink и других технологий позволяет эффективно решать задачи отслеживания изменений состояния системных компонентов, таких как сетевые интерфейсы. Это пример одного из подходов, который можно использовать для достижения поставленной цели без необходимости периодической проверки состояния с помощью поллинга.

Кроме того, если вам нужно отслеживать изменения в других атрибутах sysfs, которые могут поддерживать поллинг, вы могли бы использовать другие системные средства, такие как sysfs_notify в драйверах, чтобы сделать эти атрибуты "pollable". Важно учитывать специфические требования вашей задачи для выбора наилучшего решения.

Оцените материал
Добавить комментарий

Капча загружается...