【问题标题】:Request list of attached devices from netlink socket从 netlink 套接字请求连接设备列表
【发布时间】:2020-06-15 13:17:41
【问题描述】:

我正在开发一个用于嵌入式设备的 C++ 应用程序,该应用程序通过 netlink 套接字侦听 USB 热插拔事件。检测事件完美无缺,但另外我想在程序开始时查询已经连接的设备。我能够为网络接口存档相同的功能,但似乎 USB 是一个完全不同的故事。所以我的问题是:

  • 是否甚至可以使用 netlink 套接字列出已连接的 USB 设备?
  • 如果可能,请求消息会是什么样子?
  • 如果不可能,那么什么是依赖很少的好选择?

用于接收热插拔事件的 MWE:

#include <sys/signalfd.h>
#include <csignal>
#include <linux/netlink.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <poll.h>

int main() {
    struct sockaddr_nl addr = {0};
    char buffer[4096];
    sigset_t signal_set;
    struct signalfd_siginfo signal_info;
    struct pollfd pfd[2];
    int ret_poll;
    ssize_t n;

    // Set signals we want to catch
    sigemptyset(&signal_set);
    sigaddset(&signal_set, SIGTERM);
    sigaddset(&signal_set, SIGINT);

    // Change the signal mask and check
    if (sigprocmask(SIG_BLOCK, &signal_set, nullptr) < 0) {
        fprintf(stderr, "Error while sigprocmask(): %s\n", strerror(errno));
        return EXIT_FAILURE;
    }
    // Get a signal file descriptor
    pfd[0].fd = signalfd(-1, &signal_set, 0);
    // Check the signal file descriptor
    if (pfd[0].fd < 0) {
        fprintf(stderr, "Error while signalfd(): %s\n", strerror(errno));
        return EXIT_FAILURE;
    }

    // Create a netlink socket
    pfd[1].fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
    if (pfd[1].fd < 0) {
        fprintf(stderr, "Netlink socket create failed: %s\n", strerror(errno));
        return EXIT_FAILURE;
    }

    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    addr.nl_groups = 2;

    if (bind(pfd[1].fd, (struct sockaddr *) &addr, sizeof(addr))) {
        fprintf(stderr, "Netlink socket bind() failed: %s\n", strerror(errno));
        return EXIT_FAILURE;
    }

    pfd[0].events = POLLIN;
    pfd[1].events = POLLIN;

    while (true) {
        // Wait for events without time limit
        ret_poll = poll(pfd, 2, -1);
        if (ret_poll < 0) {
            fprintf(stderr, "SystemMaster::execute() -> "
                            "Error while poll(): %s\n", strerror(errno));
            return EXIT_FAILURE;
        }
        // True, if a signal from the operating system was sent to this process
        if (pfd[0].revents & POLLIN) {
            // Get the signal
            n = read(pfd[0].fd, &signal_info, sizeof(signal_info));
            // True, if an error occurred while getting the signal
            if (n == -1) {
                fprintf(stderr, "Error while read() on signal pipe: %s\n", strerror(errno));
            }
            // Check, if we are really interested in the caught signal
            if ((signal_info.ssi_signo == SIGTERM) || (signal_info.ssi_signo == SIGINT)) {
                printf("Signal received\n");
            }
            break;
        }
        // True, if a netlink message is available
        if (pfd[1].revents & POLLIN) {
            n = recv(pfd[1].fd, &buffer, sizeof(buffer), 0);

            for (int i = 0; i < n; ++i) {
                if (buffer[i] == 0) printf("\n");
                else if (buffer[i] > 33 && buffer[i] < 126) printf("%c", buffer[i]);
            }
        }
    }
    // Close both file descriptors
    close(pfd[0].fd);
    close(pfd[1].fd);
    return 0;
}

提前感谢您的任何回复!

【问题讨论】:

  • 查看lsusb 及其来源。
  • @UlrichEckhardt,我什至建议使用它。

标签: c++ linux linux-kernel embedded-linux netlink


【解决方案1】:

按照@UlrichEckhardt 的建议,我检查了 lsusb 及其来源。它使用libusb,其中Linux 部分似乎使用libudev,如果libudev 不可用,则退回到netlink 套接字进行热插拔检测。似乎仅使用 netlink 时,扫描设备的可能性不可用。但是,没有任何保证,因为我没有深入研究以检查所有内容。

无论如何,我决定使用 libudev 进行扫描和热插拔,因为我喜欢统一的解决方案。代码如下:

#include <unistd.h>
#include <poll.h>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <sys/signalfd.h>
#include <csignal>
#include <libudev.h>

void scanDevices(struct udev *udev) {
    struct udev_device *device;
    struct udev_enumerate *enumerate;
    struct udev_list_entry *devices, *dev_list_entry;

    // Create enumerate object
    enumerate = udev_enumerate_new(udev);
    if (!enumerate) {
        printf("Error while creating udev enumerate\n");
        return;
    }

    // Scan devices
    udev_enumerate_scan_devices(enumerate);

    // Fill up device list
    devices = udev_enumerate_get_list_entry(enumerate);
    if (!devices) {
        printf("Error while getting device list\n");
        return;
    }

    udev_list_entry_foreach(dev_list_entry, devices) {
        // Get the device
        device = udev_device_new_from_syspath(udev, udev_list_entry_get_name(dev_list_entry));
        // Print device information
        printf("DEVNODE=%s\n", udev_device_get_devnode(device));
        printf("KERNEL=%s\n", udev_device_get_sysname(device));
        printf("DEVPATH=%s\n", udev_device_get_devpath(device));
        printf("DEVTYPE=%s\n\n", udev_device_get_devtype(device));
        // Free the device
        udev_device_unref(device);
    }
    // Free enumerate
    udev_enumerate_unref(enumerate);
}

void monitorDevices(int signal_fd, struct udev *udev) {
    udev_monitor *monitor = udev_monitor_new_from_netlink(udev, "udev");
    struct pollfd pfd[2];
    int ret_poll;
    ssize_t  n;

    // Enable receiving hotplug events
    udev_monitor_enable_receiving(monitor);

    pfd[0].events = POLLIN;
    pfd[0].fd = signal_fd;
    pfd[1].events = POLLIN;
    pfd[1].fd = udev_monitor_get_fd(monitor);
    if (pfd[1].fd < 0) {
        printf("Error while getting hotplug monitor\n");
        udev_monitor_unref(monitor);
        return;
    }

    while (true) {
        // Wait for events without time limit
        ret_poll = poll(pfd, 2, -1);
        if (ret_poll < 0) {
            printf("Error while polling file descriptors\n");
            break;
        }
        // True, if a signal from the operating system was sent to this process
        if (pfd[0].revents & POLLIN) {
            struct signalfd_siginfo signal_info;
            // Get the signal
            n = read(pfd[0].fd, &signal_info, sizeof(signal_info));
            // True, if an error occurred while getting the signal
            if (n == -1) {
                printf("Error while read on signal file descriptor\n");
                break;
            }
            // Check which signal was caught
            switch (signal_info.ssi_signo) {
                case SIGINT:
                    printf("SIGINT received\n");
                    break;

                case SIGTERM:
                    printf("SIGTERM received\n");
                    break;

                default:
                    printf("Unknown signal received\n");
            }
            break;
        }
        if (pfd[1].revents & POLLIN) {
            // Get the device
            struct udev_device *device = udev_monitor_receive_device(monitor);
            if (!device) {
                printf("Error while getting device...returning to work\n");
                continue;
            }
            // Print device information
            printf("DEVNODE=%s\n", udev_device_get_devnode(device));
            printf("KERNEL=%s\n", udev_device_get_sysname(device));
            printf("DEVPATH=%s\n", udev_device_get_devpath(device));
            printf("DEVTYPE=%s\n\n", udev_device_get_devtype(device));
            // Free the device
            udev_device_unref(device);
        }
    }
    // Free the monitor
    udev_monitor_unref(monitor);
}

int main() {
    // Create a new udev object
    struct udev *udev = udev_new();
    if (!udev) {
        printf("Error while initialization!\n");
        return EXIT_FAILURE;
    }

    sigset_t mask;

    // Set signals we want to catch
    sigemptyset(&mask);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGINT);

    // Change the signal mask and check
    if (sigprocmask(SIG_BLOCK, &mask, nullptr) < 0) {
        fprintf(stderr, "Error while sigprocmask(): %s\n", std::strerror(errno));
        return EXIT_FAILURE;
    }
    // Get a signal file descriptor
    int signal_fd = signalfd(-1, &mask, 0);
    // Check the signal file descriptor
    if (signal_fd < 0) {
        fprintf(stderr, "Error while signalfd(): %s\n", std::strerror(errno));
        return EXIT_FAILURE;
    }
    // First scan already attached devices
    scanDevices(udev);
    // Second monitor hotplug events
    monitorDevices(signal_fd, udev);
    // Free the udev object
    udev_unref(udev);
}

我真的很想对所有事情都使用 netlink,因为我完整程序的其他部分也使用它。如果有人知道使用 netlink 解决我的主要问题的可能性,我将非常感谢分享。

【讨论】:

  • 你为什么不能使用libusb
  • @0andriy 我希望尽可能少的依赖。无论如何我都需要 libudev,因为 libusb 使用它,而且我看到,对于我的具体情况,使用 libusb 没有任何优势可以证明额外的依赖是合理的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-09
  • 1970-01-01
  • 2020-06-09
相关资源
最近更新 更多