[alsa-devel] [alsa-utils][PATCH 0/9] alsactl: friendly to pluggable devices
Hi,
A bug was reported that monitor mode of alsactl consumes much CPU time after disconnection of any sound card[1].
This patchset improves the mode to handle connection/disconnection of sound card. Observed control nodes are maintained by list structure instead of array. Linux specific epoll(7) is used to dispatch events. Linux specific inotify(7) is used to detect connection of new sound card. Linux specific signalfd(2) is used to catch Unix signals for termination.
[1] http://mailman.alsa-project.org/pipermail/alsa-devel/2018-September/140580.h...
Takashi Sakamoto (9): alsactl: split event loop code to a function alsactl: add an iterator of registered instances of sound card alsactl: use epoll(7) instead of poll(2) alsactl: use link list to maintain source of events alsactl: use a list of source for event dispatcher instead of an array of source alsactl: obsolete array for maintenance of handlers alsactl: handle disconnection of sound card alsactl: handle detection of new sound card alsactl: use signalfd to catch UNIX signal
alsactl/monitor.c | 422 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 375 insertions(+), 47 deletions(-)
In a mode of 'monitor', an event loop runs.
This commit applies a small refactoring to splits the loop into a function for readability.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- alsactl/monitor.c | 51 ++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 21 deletions(-)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index 8351a79..fb3fc18 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -84,6 +84,35 @@ static int print_event(int card, snd_ctl_t *ctl) return 0; }
+static int run_dispatcher(snd_ctl_t **ctls, int ncards, int show_cards) +{ + int err = 0; + + for (;ncards > 0;) { + struct pollfd fds[ncards]; + int i; + + for (i = 0; i < ncards; i++) + snd_ctl_poll_descriptors(ctls[i], &fds[i], 1); + + err = poll(fds, ncards, -1); + if (err <= 0) { + err = 0; + break; + } + + for (i = 0; i < ncards; i++) { + unsigned short revents; + snd_ctl_poll_descriptors_revents(ctls[i], &fds[i], 1, + &revents); + if (revents & POLLIN) + print_event(show_cards ? i : -1, ctls[i]); + } + } + + return err; +} + #define MAX_CARDS 256
int monitor(const char *name) @@ -117,27 +146,7 @@ int monitor(const char *name) show_cards = 0; }
- for (;ncards > 0;) { - struct pollfd fds[ncards]; - - for (i = 0; i < ncards; i++) - snd_ctl_poll_descriptors(ctls[i], &fds[i], 1); - - err = poll(fds, ncards, -1); - if (err <= 0) { - err = 0; - break; - } - - for (i = 0; i < ncards; i++) { - unsigned short revents; - snd_ctl_poll_descriptors_revents(ctls[i], &fds[i], 1, - &revents); - if (revents & POLLIN) - print_event(show_cards ? i : -1, ctls[i]); - } - } - + err = run_dispatcher(ctls, ncards, show_cards); error: for (i = 0; i < ncards; i++) snd_ctl_close(ctls[i]);
In a mode of 'monitor', when given no argument, all of available control node is observed for their events. At present, discovering the nodes is done according to sound card number, instead of listing nodes in configuration space of alsa-lib.
This commit adds a structure to discover sound cards with a simple interface.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- alsactl/monitor.c | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index fb3fc18..008ceb3 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -22,6 +22,36 @@ #include <stdio.h> #include <alsa/asoundlib.h>
+#define MAX_CARDS 256 + +struct snd_card_iterator { + int card; + char name[16]; +}; + +void snd_card_iterator_init(struct snd_card_iterator *iter) +{ + iter->card = -1; + memset(iter->name, 0, sizeof(iter->name)); +} + +static const char *snd_card_iterator_next(struct snd_card_iterator *iter) +{ + if (snd_card_next(&iter->card) < 0) + return NULL; + if (iter->card < 0) + return NULL; + if (iter->card >= MAX_CARDS) { + fprintf(stderr, "alsactl: too many cards\n"); + return NULL; + } + + + snprintf(iter->name, sizeof(iter->name), "hw:%d", iter->card); + + return (const char *)iter->name; +} + static int open_ctl(const char *name, snd_ctl_t **ctlp) { snd_ctl_t *ctl; @@ -113,8 +143,6 @@ static int run_dispatcher(snd_ctl_t **ctls, int ncards, int show_cards) return err; }
-#define MAX_CARDS 256 - int monitor(const char *name) { snd_ctl_t *ctls[MAX_CARDS]; @@ -123,15 +151,11 @@ int monitor(const char *name) int i, err = 0;
if (!name) { - int card = -1; - while (snd_card_next(&card) >= 0 && card >= 0) { - char cardname[16]; - if (ncards >= MAX_CARDS) { - fprintf(stderr, "alsactl: too many cards\n"); - err = -E2BIG; - goto error; - } - sprintf(cardname, "hw:%d", card); + struct snd_card_iterator iter; + const char *cardname; + + snd_card_iterator_init(&iter); + while ((cardname = snd_card_iterator_next(&iter))) { err = open_ctl(cardname, &ctls[ncards]); if (err < 0) goto error;
Linux kernel supports unique system call; epoll(7). This allows applications to make associations for descriptor-unique data in a easy way.
This commit uses epoll(7) instead of poll(2) for this point.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- alsactl/monitor.c | 134 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 113 insertions(+), 21 deletions(-)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index 008ceb3..7050eeb 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -20,6 +20,9 @@ #include "aconfig.h" #include "version.h" #include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <sys/epoll.h> #include <alsa/asoundlib.h>
#define MAX_CARDS 256 @@ -114,41 +117,121 @@ static int print_event(int card, snd_ctl_t *ctl) return 0; }
-static int run_dispatcher(snd_ctl_t **ctls, int ncards, int show_cards) +static int operate_dispatcher(int epfd, uint32_t op, struct epoll_event *epev, + snd_ctl_t *ctl) { + struct pollfd *pfds; + int count; + unsigned int pfd_count; + int i; + int err; + + count = snd_ctl_poll_descriptors_count(ctl); + if (count < 0) + return count; + if (count == 0) + return -ENXIO; + pfd_count = count; + + pfds = calloc(pfd_count, sizeof(*pfds)); + if (!pfds) + return -ENOMEM; + + count = snd_ctl_poll_descriptors(ctl, pfds, pfd_count); + if (count < 0) { + err = count; + goto end; + } + if (count != pfd_count) { + err = -EIO; + goto end; + } + + for (i = 0; i < pfd_count; ++i) { + err = epoll_ctl(epfd, op, pfds[i].fd, epev); + if (err < 0) + break; + } +end: + free(pfds); + return err; +} + +static int prepare_dispatcher(int epfd, snd_ctl_t **ctls, int ncards) +{ + int i; int err = 0;
- for (;ncards > 0;) { - struct pollfd fds[ncards]; - int i; + for (i = 0; i < ncards; ++i) { + snd_ctl_t *ctl = ctls[i]; + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.ptr = (void *)ctl; + err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, ctl); + if (err < 0) + break; + }
- for (i = 0; i < ncards; i++) - snd_ctl_poll_descriptors(ctls[i], &fds[i], 1); + return err; +}
- err = poll(fds, ncards, -1); - if (err <= 0) { - err = 0; +static int run_dispatcher(int epfd, unsigned int max_ev_count, int show_cards) +{ + struct epoll_event *epev; + int err = 0; + + epev = calloc(max_ev_count, sizeof(*epev)); + if (!epev) + return -ENOMEM; + + while (true) { + int count; + int i; + + count = epoll_wait(epfd, epev, max_ev_count, 200); + if (count < 0) { + err = count; break; } + if (count == 0) + continue; + + for (i = 0; i < count; ++i) { + struct epoll_event *ev = epev + i; + snd_ctl_t *handle = (snd_ctl_t *)ev->data.ptr;
- for (i = 0; i < ncards; i++) { - unsigned short revents; - snd_ctl_poll_descriptors_revents(ctls[i], &fds[i], 1, - &revents); - if (revents & POLLIN) - print_event(show_cards ? i : -1, ctls[i]); + if (ev->events & EPOLLIN) + print_event(show_cards ? i : -1, handle); } }
+ free(epev); + return err; }
+static void clear_dispatcher(int epfd, snd_ctl_t **ctls, int ncards) +{ + int i; + + for (i = 0; i < ncards; ++i) { + snd_ctl_t *ctl = ctls[i]; + operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, ctl); + } +} + int monitor(const char *name) { - snd_ctl_t *ctls[MAX_CARDS]; + snd_ctl_t *ctls[MAX_CARDS] = {0}; int ncards = 0; int show_cards; - int i, err = 0; + int epfd; + int i; + int err = 0; + + epfd = epoll_create(1); + if (epfd < 0) + return -errno;
if (!name) { struct snd_card_iterator iter; @@ -170,9 +253,18 @@ int monitor(const char *name) show_cards = 0; }
- err = run_dispatcher(ctls, ncards, show_cards); - error: - for (i = 0; i < ncards; i++) - snd_ctl_close(ctls[i]); + err = prepare_dispatcher(epfd, ctls, ncards); + if (err >= 0) + err = run_dispatcher(epfd, ncards, show_cards); + clear_dispatcher(epfd, ctls, ncards); + +error: + for (i = 0; i < ncards; i++) { + if (ctls[i]) + snd_ctl_close(ctls[i]); + } + + close(epfd); + return err; }
At present, handlers for control nodes are maintained by one-dimensional array. This is not necessarily useful to maintain handlers with associated information.
This commit adds link-list for the maintenance.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- alsactl/monitor.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index 7050eeb..7a61f9c 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -25,6 +25,16 @@ #include <sys/epoll.h> #include <alsa/asoundlib.h>
+#include <stddef.h> +#include "list.h" + +struct src_entry { + snd_ctl_t *handle; + char *name; + unsigned int pfd_count; + struct list_head list; +}; + #define MAX_CARDS 256
struct snd_card_iterator { @@ -55,6 +65,59 @@ static const char *snd_card_iterator_next(struct snd_card_iterator *iter) return (const char *)iter->name; }
+static void remove_source_entry(struct src_entry *entry) +{ + list_del(&entry->list); + free(entry->name); + free(entry); +} + +static void clear_source_list(struct list_head *srcs) +{ + struct src_entry *entry, *tmp; + + list_for_each_entry_safe(entry, tmp, srcs, list) + remove_source_entry(entry); +} + +static int insert_source_entry(struct list_head *srcs, snd_ctl_t *handle, + const char *name) +{ + struct src_entry *entry; + int count; + int err; + + entry = calloc(1, sizeof(*entry)); + if (!entry) + return -ENOMEM; + INIT_LIST_HEAD(&entry->list); + entry->handle = handle; + + entry->name = strdup(name); + if (!entry->name) { + err = -ENOMEM; + goto error; + } + + count = snd_ctl_poll_descriptors_count(handle); + if (count < 0) { + err = count; + goto error; + } + if (count == 0) { + err = -ENXIO; + goto error; + } + entry->pfd_count = count; + + list_add_tail(&entry->list, srcs); + + return 0; +error: + remove_source_entry(entry); + return err; +} + static int open_ctl(const char *name, snd_ctl_t **ctlp) { snd_ctl_t *ctl; @@ -222,6 +285,7 @@ static void clear_dispatcher(int epfd, snd_ctl_t **ctls, int ncards)
int monitor(const char *name) { + LIST_HEAD(srcs); snd_ctl_t *ctls[MAX_CARDS] = {0}; int ncards = 0; int show_cards; @@ -240,6 +304,9 @@ int monitor(const char *name) snd_card_iterator_init(&iter); while ((cardname = snd_card_iterator_next(&iter))) { err = open_ctl(cardname, &ctls[ncards]); + if (err < 0) + goto error; + err = insert_source_entry(&srcs, ctls[ncards], cardname); if (err < 0) goto error; ncards++; @@ -247,6 +314,9 @@ int monitor(const char *name) show_cards = 1; } else { err = open_ctl(name, &ctls[0]); + if (err < 0) + goto error; + err = insert_source_entry(&srcs, ctls[ncards], name); if (err < 0) goto error; ncards++; @@ -259,6 +329,7 @@ int monitor(const char *name) clear_dispatcher(epfd, ctls, ncards);
error: + clear_source_list(&srcs); for (i = 0; i < ncards; i++) { if (ctls[i]) snd_ctl_close(ctls[i]);
In a previous commit, handlers of control nodes are maintained by link list.
This commit uses the list to register/unregister event sources to dispatcher.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- alsactl/monitor.c | 78 +++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 46 deletions(-)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index 7a61f9c..878d003 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -68,6 +68,8 @@ static const char *snd_card_iterator_next(struct snd_card_iterator *iter) static void remove_source_entry(struct src_entry *entry) { list_del(&entry->list); + if (entry->handle) + snd_ctl_close(entry->handle); free(entry->name); free(entry); } @@ -138,7 +140,7 @@ static int open_ctl(const char *name, snd_ctl_t **ctlp) return 0; }
-static int print_event(int card, snd_ctl_t *ctl) +static int print_event(snd_ctl_t *ctl, const char *name) { snd_ctl_event_t *event; unsigned int mask; @@ -152,9 +154,8 @@ static int print_event(int card, snd_ctl_t *ctl) if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM) return 0;
- if (card >= 0) - printf("card %d, ", card); - printf("#%d (%i,%i,%i,%s,%i)", + printf("node %s, #%d (%i,%i,%i,%s,%i)", + name, snd_ctl_event_elem_get_numid(event), snd_ctl_event_elem_get_interface(event), snd_ctl_event_elem_get_device(event), @@ -181,36 +182,28 @@ static int print_event(int card, snd_ctl_t *ctl) }
static int operate_dispatcher(int epfd, uint32_t op, struct epoll_event *epev, - snd_ctl_t *ctl) + struct src_entry *entry) { struct pollfd *pfds; int count; - unsigned int pfd_count; int i; - int err; - - count = snd_ctl_poll_descriptors_count(ctl); - if (count < 0) - return count; - if (count == 0) - return -ENXIO; - pfd_count = count; + int err = 0;
- pfds = calloc(pfd_count, sizeof(*pfds)); + pfds = calloc(entry->pfd_count, sizeof(*pfds)); if (!pfds) return -ENOMEM;
- count = snd_ctl_poll_descriptors(ctl, pfds, pfd_count); + count = snd_ctl_poll_descriptors(entry->handle, pfds, entry->pfd_count); if (count < 0) { err = count; goto end; } - if (count != pfd_count) { + if (count != entry->pfd_count) { err = -EIO; goto end; }
- for (i = 0; i < pfd_count; ++i) { + for (i = 0; i < entry->pfd_count; ++i) { err = epoll_ctl(epfd, op, pfds[i].fd, epev); if (err < 0) break; @@ -220,17 +213,16 @@ end: return err; }
-static int prepare_dispatcher(int epfd, snd_ctl_t **ctls, int ncards) +static int prepare_dispatcher(int epfd, struct list_head *srcs) { - int i; + struct src_entry *entry; int err = 0;
- for (i = 0; i < ncards; ++i) { - snd_ctl_t *ctl = ctls[i]; + list_for_each_entry(entry, srcs, list) { struct epoll_event ev; ev.events = EPOLLIN; - ev.data.ptr = (void *)ctl; - err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, ctl); + ev.data.ptr = (void *)entry; + err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry); if (err < 0) break; } @@ -238,11 +230,17 @@ static int prepare_dispatcher(int epfd, snd_ctl_t **ctls, int ncards) return err; }
-static int run_dispatcher(int epfd, unsigned int max_ev_count, int show_cards) +static int run_dispatcher(int epfd, struct list_head *srcs) { + struct src_entry *entry; + unsigned int max_ev_count; struct epoll_event *epev; int err = 0;
+ max_ev_count = 0; + list_for_each_entry(entry, srcs, list) + max_ev_count += entry->pfd_count; + epev = calloc(max_ev_count, sizeof(*epev)); if (!epev) return -ENOMEM; @@ -261,10 +259,9 @@ static int run_dispatcher(int epfd, unsigned int max_ev_count, int show_cards)
for (i = 0; i < count; ++i) { struct epoll_event *ev = epev + i; - snd_ctl_t *handle = (snd_ctl_t *)ev->data.ptr; - + struct src_entry *entry = (struct src_entry *)ev->data.ptr; if (ev->events & EPOLLIN) - print_event(show_cards ? i : -1, handle); + print_event(entry->handle, entry->name); } }
@@ -273,14 +270,12 @@ static int run_dispatcher(int epfd, unsigned int max_ev_count, int show_cards) return err; }
-static void clear_dispatcher(int epfd, snd_ctl_t **ctls, int ncards) +static void clear_dispatcher(int epfd, struct list_head *srcs) { - int i; + struct src_entry *entry;
- for (i = 0; i < ncards; ++i) { - snd_ctl_t *ctl = ctls[i]; - operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, ctl); - } + list_for_each_entry(entry, srcs, list) + operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry); }
int monitor(const char *name) @@ -288,9 +283,7 @@ int monitor(const char *name) LIST_HEAD(srcs); snd_ctl_t *ctls[MAX_CARDS] = {0}; int ncards = 0; - int show_cards; int epfd; - int i; int err = 0;
epfd = epoll_create(1); @@ -311,7 +304,6 @@ int monitor(const char *name) goto error; ncards++; } - show_cards = 1; } else { err = open_ctl(name, &ctls[0]); if (err < 0) @@ -320,20 +312,14 @@ int monitor(const char *name) if (err < 0) goto error; ncards++; - show_cards = 0; }
- err = prepare_dispatcher(epfd, ctls, ncards); + err = prepare_dispatcher(epfd, &srcs); if (err >= 0) - err = run_dispatcher(epfd, ncards, show_cards); - clear_dispatcher(epfd, ctls, ncards); - + err = run_dispatcher(epfd, &srcs); + clear_dispatcher(epfd, &srcs); error: clear_source_list(&srcs); - for (i = 0; i < ncards; i++) { - if (ctls[i]) - snd_ctl_close(ctls[i]); - }
close(epfd);
In former commits, handlers of control node are maintained by link list, instead of one-dimensional array.
This commit obsoletes the array and split source preparation to a function.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- alsactl/monitor.c | 65 ++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 32 deletions(-)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index 878d003..09ef63c 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -35,8 +35,6 @@ struct src_entry { struct list_head list; };
-#define MAX_CARDS 256 - struct snd_card_iterator { int card; char name[16]; @@ -54,11 +52,6 @@ static const char *snd_card_iterator_next(struct snd_card_iterator *iter) return NULL; if (iter->card < 0) return NULL; - if (iter->card >= MAX_CARDS) { - fprintf(stderr, "alsactl: too many cards\n"); - return NULL; - } -
snprintf(iter->name, sizeof(iter->name), "hw:%d", iter->card);
@@ -140,6 +133,36 @@ static int open_ctl(const char *name, snd_ctl_t **ctlp) return 0; }
+static int prepare_source_entry(struct list_head *srcs, const char *name) +{ + snd_ctl_t *handle; + int err; + + if (!name) { + struct snd_card_iterator iter; + const char *cardname; + + snd_card_iterator_init(&iter); + while ((cardname = snd_card_iterator_next(&iter))) { + err = open_ctl(cardname, &handle); + if (err < 0) + return err; + err = insert_source_entry(srcs, handle, cardname); + if (err < 0) + return err; + } + } else { + err = open_ctl(name, &handle); + if (err < 0) + return err; + err = insert_source_entry(srcs, handle, name); + if (err < 0) + return err; + } + + return 0; +} + static int print_event(snd_ctl_t *ctl, const char *name) { snd_ctl_event_t *event; @@ -281,8 +304,6 @@ static void clear_dispatcher(int epfd, struct list_head *srcs) int monitor(const char *name) { LIST_HEAD(srcs); - snd_ctl_t *ctls[MAX_CARDS] = {0}; - int ncards = 0; int epfd; int err = 0;
@@ -290,29 +311,9 @@ int monitor(const char *name) if (epfd < 0) return -errno;
- if (!name) { - struct snd_card_iterator iter; - const char *cardname; - - snd_card_iterator_init(&iter); - while ((cardname = snd_card_iterator_next(&iter))) { - err = open_ctl(cardname, &ctls[ncards]); - if (err < 0) - goto error; - err = insert_source_entry(&srcs, ctls[ncards], cardname); - if (err < 0) - goto error; - ncards++; - } - } else { - err = open_ctl(name, &ctls[0]); - if (err < 0) - goto error; - err = insert_source_entry(&srcs, ctls[ncards], name); - if (err < 0) - goto error; - ncards++; - } + err = prepare_source_entry(&srcs, name); + if (err < 0) + goto error;
err = prepare_dispatcher(epfd, &srcs); if (err >= 0)
Once sound card becomes disconnection state, corresponding control node becomes to emit error event for listeners. When catching this type of event, event dispatcher should stop observation of the node. However, at present, a mode of monitor can't handle this correctly. As a result, poll(2) is executed quite frequently in loop with no wait. This results 100% consumption of CPU time.
This commit takes the dispatcher to remove the node from observation list when detecting the disconnection state.
Reported-by: Thomas Gläßle thomas@coldfix.de Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- alsactl/monitor.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index 09ef63c..559fb4c 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -285,6 +285,10 @@ static int run_dispatcher(int epfd, struct list_head *srcs) struct src_entry *entry = (struct src_entry *)ev->data.ptr; if (ev->events & EPOLLIN) print_event(entry->handle, entry->name); + if (ev->events & EPOLLERR) { + operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry); + remove_source_entry(entry); + } } }
At present, plug-and-play is not supported in a mode of 'monitor', thus new sound card is not handled during runtime. This is not happy.
This commit uses Linux-specific inotify(7) to monitor '/dev/snd' directory. When some files are newly added to the directory, event dispatcher is suspended. Event sources are scanned again and the dispatcher continue to run.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- alsactl/monitor.c | 118 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 10 deletions(-)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index 559fb4c..a3c3ea4 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -23,6 +23,9 @@ #include <stdbool.h> #include <string.h> #include <sys/epoll.h> +#include <sys/inotify.h> +#include <limits.h> +#include <time.h> #include <alsa/asoundlib.h>
#include <stddef.h> @@ -133,6 +136,18 @@ static int open_ctl(const char *name, snd_ctl_t **ctlp) return 0; }
+static inline bool seek_entry_by_name(struct list_head *srcs, const char *name) +{ + struct src_entry *entry; + + list_for_each_entry(entry, srcs, list) { + if (!strcmp(entry->name, name)) + return true; + } + + return false; +} + static int prepare_source_entry(struct list_head *srcs, const char *name) { snd_ctl_t *handle; @@ -144,6 +159,8 @@ static int prepare_source_entry(struct list_head *srcs, const char *name)
snd_card_iterator_init(&iter); while ((cardname = snd_card_iterator_next(&iter))) { + if (seek_entry_by_name(srcs, cardname)) + continue; err = open_ctl(cardname, &handle); if (err < 0) return err; @@ -152,6 +169,8 @@ static int prepare_source_entry(struct list_head *srcs, const char *name) return err; } } else { + if (seek_entry_by_name(srcs, name)) + return 0; err = open_ctl(name, &handle); if (err < 0) return err; @@ -163,6 +182,41 @@ static int prepare_source_entry(struct list_head *srcs, const char *name) return 0; }
+static int check_control_cdev(int infd, bool *retry) +{ + struct inotify_event *ev; + char *buf; + int err = 0; + + buf = calloc(1, sizeof(*ev) + NAME_MAX); + if (!buf) + return -ENOMEM; + + while (1) { + ssize_t len = read(infd, buf, sizeof(*ev) + NAME_MAX); + if (len < 0) { + if (errno != EAGAIN) + err = errno; + break; + } else if (len == 0) { + break; + } + + size_t pos = 0; + while (pos < len) { + ev = (struct inotify_event *)(buf + pos); + if ((ev->mask & IN_CREATE) && + strstr(ev->name, "controlC") == ev->name) + *retry = true; + pos += sizeof(*ev) + ev->len; + } + } + + free(buf); + + return err; +} + static int print_event(snd_ctl_t *ctl, const char *name) { snd_ctl_event_t *event; @@ -236,13 +290,18 @@ end: return err; }
-static int prepare_dispatcher(int epfd, struct list_head *srcs) +static int prepare_dispatcher(int epfd, int infd, struct list_head *srcs) { + struct epoll_event ev = {0}; struct src_entry *entry; int err = 0;
+ ev.events = EPOLLIN; + ev.data.fd = infd; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) < 0) + return -errno; + list_for_each_entry(entry, srcs, list) { - struct epoll_event ev; ev.events = EPOLLIN; ev.data.ptr = (void *)entry; err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry); @@ -253,7 +312,8 @@ static int prepare_dispatcher(int epfd, struct list_head *srcs) return err; }
-static int run_dispatcher(int epfd, struct list_head *srcs) +static int run_dispatcher(int epfd, int infd, struct list_head *srcs, + bool *retry) { struct src_entry *entry; unsigned int max_ev_count; @@ -282,7 +342,15 @@ static int run_dispatcher(int epfd, struct list_head *srcs)
for (i = 0; i < count; ++i) { struct epoll_event *ev = epev + i; - struct src_entry *entry = (struct src_entry *)ev->data.ptr; + + if (ev->data.fd == infd) { + err = check_control_cdev(infd, retry); + if (err < 0 || *retry) + goto end; + continue; + } + + entry = ev->data.ptr; if (ev->events & EPOLLIN) print_event(entry->handle, entry->name); if (ev->events & EPOLLERR) { @@ -290,42 +358,72 @@ static int run_dispatcher(int epfd, struct list_head *srcs) remove_source_entry(entry); } } + if (err < 0) + break; } - +end: free(epev); - return err; }
-static void clear_dispatcher(int epfd, struct list_head *srcs) +static void clear_dispatcher(int epfd, int infd, struct list_head *srcs) { struct src_entry *entry;
list_for_each_entry(entry, srcs, list) operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry); + + epoll_ctl(epfd, EPOLL_CTL_DEL, infd, NULL); }
int monitor(const char *name) { LIST_HEAD(srcs); int epfd; + int infd; + int wd = 0; + bool retry; int err = 0;
epfd = epoll_create(1); if (epfd < 0) return -errno;
+ infd = inotify_init1(IN_NONBLOCK); + if (infd < 0) { + err = -errno; + goto error; + } + wd = inotify_add_watch(infd, "/dev/snd/", IN_CREATE); + if (wd < 0) { + err = -errno; + goto error; + } +retry: + retry = false; err = prepare_source_entry(&srcs, name); if (err < 0) goto error;
- err = prepare_dispatcher(epfd, &srcs); + err = prepare_dispatcher(epfd, infd, &srcs); if (err >= 0) - err = run_dispatcher(epfd, &srcs); - clear_dispatcher(epfd, &srcs); + err = run_dispatcher(epfd, infd, &srcs, &retry); + clear_dispatcher(epfd, infd, &srcs); + + if (retry) { + // A simple makeshift for timing gap between creation of nodes + // by devtmpfs and chmod() by udevd. + struct timespec req = { .tv_sec = 1 }; + nanosleep(&req, NULL); + goto retry; + } error: clear_source_list(&srcs);
+ if (wd > 0) + inotify_rm_watch(infd, wd); + close(infd); + close(epfd);
return err;
In a mode of 'monitor, event loop runs to dispatch asynchronous event emitted by control node. In this case, UNIX signal is used to terminate the event loop.
This commit uses signalfd to catch the UNIX signal.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- alsactl/monitor.c | 57 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 7 deletions(-)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index a3c3ea4..282fb1c 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -26,6 +26,8 @@ #include <sys/inotify.h> #include <limits.h> #include <time.h> +#include <signal.h> +#include <sys/signalfd.h> #include <alsa/asoundlib.h>
#include <stddef.h> @@ -290,12 +292,18 @@ end: return err; }
-static int prepare_dispatcher(int epfd, int infd, struct list_head *srcs) +static int prepare_dispatcher(int epfd, int sigfd, int infd, + struct list_head *srcs) { struct epoll_event ev = {0}; struct src_entry *entry; int err = 0;
+ ev.events = EPOLLIN; + ev.data.fd = sigfd; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, sigfd, &ev) < 0) + return -errno; + ev.events = EPOLLIN; ev.data.fd = infd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) < 0) @@ -312,7 +320,7 @@ static int prepare_dispatcher(int epfd, int infd, struct list_head *srcs) return err; }
-static int run_dispatcher(int epfd, int infd, struct list_head *srcs, +static int run_dispatcher(int epfd, int sigfd, int infd, struct list_head *srcs, bool *retry) { struct src_entry *entry; @@ -343,6 +351,9 @@ static int run_dispatcher(int epfd, int infd, struct list_head *srcs, for (i = 0; i < count; ++i) { struct epoll_event *ev = epev + i;
+ if (ev->data.fd == sigfd) + goto end; + if (ev->data.fd == infd) { err = check_control_cdev(infd, retry); if (err < 0 || *retry) @@ -366,7 +377,8 @@ end: return err; }
-static void clear_dispatcher(int epfd, int infd, struct list_head *srcs) +static void clear_dispatcher(int epfd, int sigfd, int infd, + struct list_head *srcs) { struct src_entry *entry;
@@ -374,20 +386,49 @@ static void clear_dispatcher(int epfd, int infd, struct list_head *srcs) operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
epoll_ctl(epfd, EPOLL_CTL_DEL, infd, NULL); + + epoll_ctl(epfd, EPOLL_CTL_DEL, sigfd, NULL); +} + +static int prepare_signalfd(int *sigfd) +{ + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) + return -errno; + + fd = signalfd(-1, &mask, 0); + if (fd < 0) + return -errno; + *sigfd = fd; + + return 0; }
int monitor(const char *name) { LIST_HEAD(srcs); + int sigfd = 0; int epfd; int infd; int wd = 0; bool retry; int err = 0;
+ err = prepare_signalfd(&sigfd); + if (err < 0) + return err; + epfd = epoll_create(1); - if (epfd < 0) + if (epfd < 0) { + close(sigfd); return -errno; + }
infd = inotify_init1(IN_NONBLOCK); if (infd < 0) { @@ -405,10 +446,10 @@ retry: if (err < 0) goto error;
- err = prepare_dispatcher(epfd, infd, &srcs); + err = prepare_dispatcher(epfd, sigfd, infd, &srcs); if (err >= 0) - err = run_dispatcher(epfd, infd, &srcs, &retry); - clear_dispatcher(epfd, infd, &srcs); + err = run_dispatcher(epfd, sigfd, infd, &srcs, &retry); + clear_dispatcher(epfd, sigfd, infd, &srcs);
if (retry) { // A simple makeshift for timing gap between creation of nodes @@ -426,5 +467,7 @@ error:
close(epfd);
+ close(sigfd); + return err; }
Dne 14.10.2018 v 16:36 Takashi Sakamoto napsal(a):
Hi,
A bug was reported that monitor mode of alsactl consumes much CPU time after disconnection of any sound card[1].
This patchset improves the mode to handle connection/disconnection of sound card. Observed control nodes are maintained by list structure instead of array. Linux specific epoll(7) is used to dispatch events. Linux specific inotify(7) is used to detect connection of new sound card. Linux specific signalfd(2) is used to catch Unix signals for termination.
Thanks for your code. I reviewed this and applied to the alsa-utils git repo.
Jaroslav
participants (2)
-
Jaroslav Kysela
-
Takashi Sakamoto