C++ Как в Linux мониторить usb устройства?

121
28 декабря 2020, 21:50

У меня есть графическое GTK приложение и мне нужно получать информацию об подключенных USB девайсах, используя libusb я могу получить то что нужно но как оформить сам мониторинг, правильно ли запускать в отдельном потоке вечный цикл и обновлять поток UI? Мне интересно каким образом работают такие системы, какие есть модели управления потоками, возможно есть какие-нибудь механизмы взаимодействия с системой так как программа работает в user space, system calls?

Можно ли чтобы Linux пробудил мой поток когда подключили новый девайс?

Answer 1

Для начала поставить библиотеку libudev

sudo apt-get install libudev-dev

Код отсюда

#include <stdio.h>
#include <unistd.h>
#include <libudev.h>
int main()
{
    struct udev *udev;
    struct udev_device *dev;
    struct udev_monitor *mon;
    int fd;
    int status;
    /* create udev object */
    udev = udev_new();
    if (!udev) {
        fprintf(stderr, "Can't create udev\n");
        return 1;
    }
        //группы сообщений
        //(1)kernel - посылает событие к udevd в момент подключения устройства для его инициализации
        //(2)udev   - посылает событие в userspace программам, используемым libudev
        mon = udev_monitor_new_from_netlink(udev, "udev");
        //Можно также добавлять подсистемы pci, usb, scsi, scsi_host, scsi_generic
        //Типы устройств usb_device, usb_interface, scsi_host, scsi_device, disk, partition
        udev_monitor_filter_add_match_subsystem_devtype(mon, "block", "disk"); //флешки
        udev_monitor_enable_receiving(mon);
        fd = udev_monitor_get_fd(mon);
        printf("Start...\n");
        fflush(stdout);
        while (1) {
            fd_set fds;
            struct timeval tv;
            int ret;
            FD_ZERO(&fds);
            FD_SET(fd, &fds);
            tv.tv_sec = 0;
            tv.tv_usec = 0;
            ret = select(fd+1, &fds, NULL, NULL, &tv);
            if (ret > 0 && FD_ISSET(fd, &fds)) {
                dev = udev_monitor_receive_device(mon);
                if (dev) {
                    printf("I: ACTION=%s\n",  udev_device_get_action(dev));
                    printf("I: DEVNAME=%s\n", udev_device_get_sysname(dev));
                    printf("I: DEVPATH=%s\n", udev_device_get_devpath(dev));
                    printf("I: MACADDR=%s\n", udev_device_get_sysattr_value(dev, "address"));
                    printf("---\n");
                    fflush(stdout);
                    /* free dev */
                    udev_device_unref(dev);
                }
            }
            /* 500 milliseconds */
            usleep(500*1000);
        }
        /* free udev */
        udev_unref(udev);
    return 0;
}

Событие uevent, которое udevd получает от драйвера ядра - это GROUP_KERNEL (1), а уведомление программ из пространства пользователя /lib/udev programs or others - это GROUP_UDEV (2). Workflow отсюда

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

#include <stdio.h>
#include <unistd.h>
#include <libudev.h>
#include <signal.h>
#include <thread>
#include <sys/wait.h>

void task() {
    printf("Create thread and join my func");
    auto func = []() {
        for(auto i = 0 ; i<3 ; ++i) {
            sleep(1);
            printf("Hard calculations: %d ", i);
        }
        fflush(stdout);
    };
    std::thread t(func);
    t.join();
}

int main()
{
    struct udev *udev;
    struct udev_device *dev;
    struct udev_monitor *mon;
    int fd;
    int status;
    /* create udev object */
    udev = udev_new();
    if (!udev) {
        fprintf(stderr, "Can't create udev\n");
        return 1;
    }
        pid_t pid = fork();
        switch (pid) {
        case -1: exit(EXIT_FAILURE); /*Ошибко*/
        case 0: {                    /*Выполнение дочернего процесса*/
            //группы сообщений
            //kernel - посылает событие к udevd в момент подключения устройства для его инициализации
            //udev   - посылает событие в userspace программам, используемым libudev
            mon = udev_monitor_new_from_netlink(udev, "udev");
            //Можно также добавлять подсистемы pci, usb, scsi, scsi_host, scsi_generic
            //Типы устройств usb_device, usb_interface, scsi_host, scsi_device, disk, partition
            udev_monitor_filter_add_match_subsystem_devtype(mon, "block", "disk"); //флешки
            udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", "usb_device"); //флешки
            udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", "usb_interface"); //флешки
            udev_monitor_enable_receiving(mon);
            fd = udev_monitor_get_fd(mon);
            printf("Start...\n");
            fflush(stdout);
            while (1) {
                fd_set fds;
                int ret;
                FD_ZERO(&fds);
                FD_SET(fd, &fds);
                ret = select(fd+1, &fds, nullptr, nullptr, nullptr);
                if (ret > 0 && FD_ISSET(fd, &fds)) {
                    dev = udev_monitor_receive_device(mon);
                    if (dev) {
                        printf("I: ACTION=%s\n",  udev_device_get_action(dev));
                        printf("I: DEVNAME=%s\n", udev_device_get_sysname(dev));
                        printf("I: DEVPATH=%s\n", udev_device_get_devpath(dev));
                        printf("I: MACADDR=%s\n", udev_device_get_sysattr_value(dev, "address"));
                        printf("---\n");
                        fflush(stdout);
                        if(strncmp(udev_device_get_action(dev), "add", 3) == 0) {
                            union sigval value;
                            value.sival_int = 1;
                            sigqueue(getppid(), SIGRTMIN, value);
                        }
                        /* free dev */
                        udev_device_unref(dev);
                    }
                }
                /* 500 milliseconds */
                usleep(500*1000);
            }
            /* free udev */
            udev_unref(udev);
        }
        default: {    /*Продолжение выполнения родительского процесса*/
            int ex;
            int sig;
            siginfo_t si;
            sigset_t allSigs;
            sigfillset(&allSigs);
            if(sigprocmask(SIG_SETMASK, &allSigs, NULL) == -1) {
                printf("fail\n");
                exit(EXIT_FAILURE);
            }
            while(1) {
                sig = sigwaitinfo(&allSigs, &si);
                printf("sig %s\n",strsignal(sig));
                if(SIGINT == sig || SIGTERM == sig) {
                    kill(pid, SIGTERM);
                    exit(EXIT_SUCCESS);
                }
                if(SIGRTMIN == sig) {
                    task();  /*Запускаем функцию, где реализуем отдельный поток*/
                }
            }
            }
        } //switch
    return 0;
}
READ ALSO
*.VC.db в проекте Visual Studio

*.VC.db в проекте Visual Studio

В проекте есть *VC

139
Можно ли сократить этот код?

Можно ли сократить этот код?

Могу ли я как то сократить этот код не теряя его функциональности?

130
Изменить свойство CSS JQuery

Изменить свойство CSS JQuery

Как изменить свойство CSS display:none у data-share--audio--top на display:block на основе выбранной кнопки?

119