Les événements hotplug de libusb-1.0 cessent de fonctionner dans les parents après fork (), lorsque l’enfant appelle libusb_exit ()

J’ai développé une application qui surveille l’arborescence des périphériques USB à l’aide de libusb_hotplug_register_callback() . Lorsqu’un périphérique correspondant à certains critères est connecté, il exec() une application fork() et exec() une application permettant de gérer ce périphérique.

L’application fonctionne bien depuis quelque temps déjà, mais je suis revenue pour essayer de la «ranger» …

libusb ouvrira un certain nombre de descripteurs de fichiers (voir ci-dessous) dont il surveille les événements, etc. Le problème est qu’après avoir appelé fork() et avant d’appeler exec() , j’aimerais fermer libusb, en fermant le des descripteurs de fichiers et en laissant les enfants dans un état de propreté.

Parent:

 root@imx6q:~# ls -l /proc/14245/fd total 0 lrwx------ 1 root root 64 Feb 9 18:15 0 -> /dev/pts/2 lrwx------ 1 root root 64 Feb 9 18:15 1 -> /dev/pts/2 lrwx------ 1 root root 64 Feb 9 18:15 2 -> /dev/pts/2 lrwx------ 1 root root 64 Feb 9 18:15 3 -> socket:[1681018] lr-x------ 1 root root 64 Feb 9 18:15 4 -> pipe:[1681019] l-wx------ 1 root root 64 Feb 9 18:15 5 -> pipe:[1681019] lr-x------ 1 root root 64 Feb 9 18:15 6 -> pipe:[1681020] l-wx------ 1 root root 64 Feb 9 18:15 7 -> pipe:[1681020] lrwx------ 1 root root 64 Feb 9 18:15 8 -> anon_inode:[timerfd] 

Enfant:

 root@imx6q:~# ls -l /proc/14248/fd total 0 lr-x------ 1 root root 64 Feb 9 18:15 0 -> /dev/null l-wx------ 1 root root 64 Feb 9 18:15 1 -> /dev/null lrwx------ 1 root root 64 Feb 9 18:15 2 -> /dev/pts/2 lr-x------ 1 root root 64 Feb 9 18:15 4 -> pipe:[1681019] l-wx------ 1 root root 64 Feb 9 18:15 5 -> pipe:[1681019] lr-x------ 1 root root 64 Feb 9 18:15 6 -> pipe:[1681020] l-wx------ 1 root root 64 Feb 9 18:15 7 -> pipe:[1681020] lrwx------ 1 root root 64 Feb 9 18:15 8 -> anon_inode:[timerfd] 

Le problème que j’ai rencontré est que, en appelant libusb_exit() dans l’enfant, le parent ne voit plus aucun événement hotplug.

J’ai essayé de ré-enregistrer mon rappel après un fork() (dans le parent) sans chance (et sans erreur).

J’ai fouillé un peu dans les sources libusb et libudev, et je me demande s’il s’agit d’un effet secondaire de linux_udev_stop_event_monitor() de libusb appelant udev_monitor_unref() … Mais hélas, il n’y a pas de ‘communication’, juste un appel à close() lorsque le nombre de références atteint zéro. Et de toute façon, le socket manquant d’en haut est très probablement le socket netlink que udev ouvre (poliment, avec SOCK_CLOEXEC ).

Vous trouverez ci-dessous un exemple d’application illustrant le problème:

 #include  #include  #include  #include  #include  libusb_context *libusb_ctx; libusb_hotplug_callback_handle cb_handle; int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev, libusb_hotplug_event event, void *user_data) { pid_t pid; char *cmd[] = { "sleep", "600", NULL }; fprintf(stderr, "event! %d\n", event); /* fork, and return if in parent */ pid = fork(); assert(pid != -1); if (pid != 0) { fprintf(stderr, "intermediate child's PID is: %d\n", pid); return 0; } /* setsid() and re-fork() to complete daemonization */ assert(setsid() != -1); pid = fork(); assert(pid != -1); if (pid != 0) { fprintf(stderr, "child's PID is: %d\n", pid); exit(0); } #if 1 /* toggle this */ fprintf(stderr, "libusb is NOT shutdown in child...\n"); #else /* shutdown libusb */ libusb_hotplug_deregister_callback(libusb_ctx, cb_handle); libusb_exit(libusb_ctx); fprintf(stderr, "libusb is shutdown in child...\n"); #endif /* now that the child has reached this point, you'll never see a hotplug event again! */ /* exec() */ assert(execvp(cmd[0], cmd) == 0); abort(); } void main(void) { pid_t pid; /* setup libusb & hotplug callback */ assert(libusb_init(&libusb_ctx) == 0); assert(libusb_hotplug_register_callback(libusb_ctx, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, LIBUSB_HOTPLUG_NO_FLAGS, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, NULL, &cb_handle) == LIBUSB_SUCCESS); pid = getpid(); fprintf(stderr, "running... parent's PID is: %d\n", pid); /* allow libusb to handle events */ while (1) { assert(libusb_handle_events_completed(NULL, NULL) == 0); } } 

Avec le ‘bascule’ de #if réglé sur 1 , je vois la session suivante (connexions 3x):

 # ./main running... parent's PID is: 14370 event! 1 intermediate child's PID is: 14372 child's PID is: 14373 libusb is NOT shutdown in child... event! 1 intermediate child's PID is: 14375 child's PID is: 14376 libusb is NOT shutdown in child... event! 1 intermediate child's PID is: 14379 child's PID is: 14380 libusb is NOT shutdown in child... ^C 

Avec le ‘bascule’ de #if réglé sur 0 , je vois la session suivante (3 connexions, seule la première est activée):

 # ./main running... parent's PID is: 14388 event! 1 intermediate child's PID is: 14390 child's PID is: 14391 libusb is shutdown in child... ^C 

Les versions du logiciel sont:

  • libusb: libusb-1.0.so.0.1.0 / 1.0.20-r1
  • libudev: libudev.so.0.13.1 / 182-r7
  • kernel: 3.14.38

Si quelqu’un a déjà vu cela auparavant, ou peut le reproduire (ou ne peut pas le reproduire!), J’apprécierais votre consortingbution. Merci d’avance!

En général, je ne recommande pas d’appeler fork à partir d’un programme portable libusb, sauf si fork est immédiatement suivi de exec. Cela est dû à des problèmes connus avec fork sur OS X. Ce problème a été abordé sur la liste de diffusion libusb-devel il y a quelque temps (2002) et il semble que j’ai oublié d’append cette mise en garde à la documentation de libusb-1.0. Je recommande d’utiliser des discussions à la place.

Sous Linux, nous héritons de toutes les mises en garde de libudev. Je ne vois rien dans leur documentation à propos de fork, donc je ne sais pas si cela devrait fonctionner ou non. Vous pouvez toujours essayer avec netlink en ajoutant –disable-udev. Cette option n’est pas recommandée pour une utilisation en production, mais elle peut aider à isoler le problème.