Message ID | 20250318104659.1114340-13-jerome.forissier@linaro.org |
---|---|
State | New |
Headers | show |
Series | Uthreads | expand |
On Tue, 18 Mar 2025 at 12:47, Jerome Forissier <jerome.forissier@linaro.org> wrote: > > Add a spawn command which runs another command in the background, as > well as a wait command to suspend the shell until one or more background > jobs have completed. The job_id environment variable is set by spawn and > wait accepts optional job ids, so that one can selectively wait on any > job. > > Example: > > => date; spawn sleep 5; spawn sleep 3; date; echo "waiting..."; wait; date > Date: 2025-02-21 (Friday) Time: 17:04:52 > Date: 2025-02-21 (Friday) Time: 17:04:52 > waiting... > Date: 2025-02-21 (Friday) Time: 17:04:57 > => > > Another example showing how background jobs can make initlizations > faster. The board is i.MX93 EVK, with one spinning HDD connected to > USB1 via a hub, and a network cable plugged into ENET1. > > # From power up / reset > u-boot=> setenv autoload 0 > u-boot=> setenv ud "usb start; dhcp" > u-boot=> time run ud > [...] > time: 8.058 seconds > > # From power up / reset > u-boot=> setenv autoload 0 > u-boot=> setenv ud "spawn usb start; spawn dhcp; wait" > u-boot=> time run ud > [...] > time: 4.475 seconds > > Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org> > --- > cmd/Kconfig | 17 +++++ > cmd/Makefile | 2 + > cmd/spawn.c | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 207 insertions(+) > create mode 100644 cmd/spawn.c > > diff --git a/cmd/Kconfig b/cmd/Kconfig > index cd391d422ae..0cbb75edfbe 100644 > --- a/cmd/Kconfig > +++ b/cmd/Kconfig > @@ -3079,4 +3079,21 @@ config CMD_MESON > help > Enable useful commands for the Meson Soc family developed by Amlogic Inc. > > +config CMD_SPAWN > + bool "spawn and wait commands" > + depends on UTHREAD > + help > + spawn runs a command in the background and sets the job_id environment > + variable. wait is used to suspend the shell execution until one or more > + jobs are complete. > + > +config CMD_SPAWN_NUM_JOBS > + int "Maximum number of simultaneous jobs for spawn" > + default 16 > + help > + Job identifiers are in the range 1..CMD_SPAWN_NUM_JOBS. In other words > + there can be no more that CMD_SPAWN_NUM_JOBS running simultaneously. > + When a jobs exits, its identifier is available to be re-used by the next > + spawn command. > + > endif > diff --git a/cmd/Makefile b/cmd/Makefile > index c1275d466c8..b61f6586157 100644 > --- a/cmd/Makefile > +++ b/cmd/Makefile > @@ -239,6 +239,8 @@ obj-$(CONFIG_CMD_SCP03) += scp03.o > > obj-$(CONFIG_HUSH_SELECTABLE) += cli.o > > +obj-$(CONFIG_CMD_SPAWN) += spawn.o > + > obj-$(CONFIG_ARM) += arm/ > obj-$(CONFIG_RISCV) += riscv/ > obj-$(CONFIG_SANDBOX) += sandbox/ > diff --git a/cmd/spawn.c b/cmd/spawn.c > new file mode 100644 > index 00000000000..f7a9f225f93 > --- /dev/null > +++ b/cmd/spawn.c > @@ -0,0 +1,188 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright (c) 2011 The Chromium OS Authors. > + */ > + > +#include <command.h> > +#include <console.h> > +#include <malloc.h> > +#include <vsprintf.h> > +#include <uthread.h> > + > +/* Spawn arguments and job index */ > +struct spa { > + int argc; > + char **argv; > + unsigned int job_idx; > +}; > + > +/* > + * uthread group identifiers for each running job > + * 0: job slot available, != 0: uthread group id > + * Note that job[0] is job_id 1, job[1] is job_id 2 etc. > + */ > +static unsigned int job[CONFIG_CMD_SPAWN_NUM_JOBS]; > +/* > + * Return values of the commands run as jobs */ > +static enum command_ret_t job_ret[CONFIG_CMD_SPAWN_NUM_JOBS]; > + > +static void spa_free(struct spa *spa) > +{ > + int i; > + > + if (!spa) > + return; > + > + for (i = 0; i < spa->argc; i++) > + free(spa->argv[i]); > + free(spa->argv); > + free(spa); > +} > + > +static struct spa *spa_create(int argc, char *const argv[]) > +{ > + struct spa *spa; > + int i; > + > + spa = calloc(1, sizeof(*spa)); > + if (!spa) > + return NULL; > + spa->argc = argc; > + spa->argv = malloc(argc * sizeof(char *)); > + if (!spa->argv) > + goto err; > + for (i = 0; i < argc; i++) { > + spa->argv[i] = strdup(argv[i]); > + if (!spa->argv[i]) > + goto err; > + } > + return spa; > +err: > + spa_free(spa); > + return NULL; > +} > + > +static void spawn_thread(void *arg) > +{ > + struct spa *spa = (struct spa *)arg; > + ulong cycles = 0; > + int repeatable = 0; > + > + job_ret[spa->job_idx] = cmd_process(0, spa->argc, spa->argv, > + &repeatable, &cycles); > + spa_free(spa); > +} > + > +static unsigned int next_job_id(void) > +{ > + int i; > + > + for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++) > + if (!job[i]) > + return i + 1; > + > + /* No job available */ > + return 0; > +} > + > +static void refresh_jobs(void) > +{ > + int i; > + > + for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++) > + if (job[i] && uthread_grp_done(job[i])) > + job[i] = 0; > + > +} > + > +static int do_spawn(struct cmd_tbl *cmdtp, int flag, int argc, > + char *const argv[]) > +{ > + unsigned int id; > + unsigned int idx; > + struct spa *spa; > + int ret; > + > + if (argc == 1) > + return CMD_RET_USAGE; > + > + spa = spa_create(argc - 1, argv + 1); > + if (!spa) > + return CMD_RET_FAILURE; > + > + refresh_jobs(); > + > + id = next_job_id(); > + if (!id) > + return CMD_RET_FAILURE; > + idx = id - 1; > + > + job[idx] = uthread_grp_new_id(); > + > + ret = uthread_create(NULL, spawn_thread, spa, 0, job[idx]); > + if (ret) { > + job[idx] = 0; > + return CMD_RET_FAILURE; > + } > + > + ret = env_set_ulong("job_id", id); > + if (ret) > + return CMD_RET_FAILURE; > + > + return CMD_RET_SUCCESS; > +} > + > +U_BOOT_CMD(spawn, CONFIG_SYS_MAXARGS, 0, do_spawn, > + "run commands and summarize execution time", > + "command [args...]\n"); > + > +static enum command_ret_t wait_job(unsigned int idx) > +{ > + int prev = disable_ctrlc(false); > + > + while (!uthread_grp_done(job[idx])) { > + if (ctrlc()) { > + puts("<INTERRUPT>\n"); > + disable_ctrlc(prev); > + return CMD_RET_FAILURE; > + } > + uthread_schedule(); > + } > + > + job[idx] = 0; > + disable_ctrlc(prev); > + > + return job_ret[idx]; > +} > + > +static int do_wait(struct cmd_tbl *cmdtp, int flag, int argc, > + char *const argv[]) > +{ > + enum command_ret_t ret = CMD_RET_SUCCESS; > + unsigned long id; > + unsigned int idx; > + int i; > + > + if (argc == 1) { > + for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++) > + if (job[idx]) > + ret = wait_job(i); > + } else { > + for (i = 1; i < argc; i++) { > + id = dectoul(argv[i], NULL); > + if (id < 0 || id > CONFIG_CMD_SPAWN_NUM_JOBS) > + return CMD_RET_USAGE; > + idx = (int)id - 1; > + ret = wait_job(idx); > + } > + } > + > + return ret; > +} > + > +U_BOOT_CMD(wait, CONFIG_SYS_MAXARGS, 0, do_wait, > + "wait for one or more jobs to complete", > + "[job_id ...]\n" > + " - Wait until all specified jobs have exited and return the\n" > + " exit status of the last job waited for. When no job_id is\n" > + " given, wait for all the background jobs.\n"); > -- > 2.43.0 > Acked-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
diff --git a/cmd/Kconfig b/cmd/Kconfig index cd391d422ae..0cbb75edfbe 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -3079,4 +3079,21 @@ config CMD_MESON help Enable useful commands for the Meson Soc family developed by Amlogic Inc. +config CMD_SPAWN + bool "spawn and wait commands" + depends on UTHREAD + help + spawn runs a command in the background and sets the job_id environment + variable. wait is used to suspend the shell execution until one or more + jobs are complete. + +config CMD_SPAWN_NUM_JOBS + int "Maximum number of simultaneous jobs for spawn" + default 16 + help + Job identifiers are in the range 1..CMD_SPAWN_NUM_JOBS. In other words + there can be no more that CMD_SPAWN_NUM_JOBS running simultaneously. + When a jobs exits, its identifier is available to be re-used by the next + spawn command. + endif diff --git a/cmd/Makefile b/cmd/Makefile index c1275d466c8..b61f6586157 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -239,6 +239,8 @@ obj-$(CONFIG_CMD_SCP03) += scp03.o obj-$(CONFIG_HUSH_SELECTABLE) += cli.o +obj-$(CONFIG_CMD_SPAWN) += spawn.o + obj-$(CONFIG_ARM) += arm/ obj-$(CONFIG_RISCV) += riscv/ obj-$(CONFIG_SANDBOX) += sandbox/ diff --git a/cmd/spawn.c b/cmd/spawn.c new file mode 100644 index 00000000000..f7a9f225f93 --- /dev/null +++ b/cmd/spawn.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011 The Chromium OS Authors. + */ + +#include <command.h> +#include <console.h> +#include <malloc.h> +#include <vsprintf.h> +#include <uthread.h> + +/* Spawn arguments and job index */ +struct spa { + int argc; + char **argv; + unsigned int job_idx; +}; + +/* + * uthread group identifiers for each running job + * 0: job slot available, != 0: uthread group id + * Note that job[0] is job_id 1, job[1] is job_id 2 etc. + */ +static unsigned int job[CONFIG_CMD_SPAWN_NUM_JOBS]; +/* + * Return values of the commands run as jobs */ +static enum command_ret_t job_ret[CONFIG_CMD_SPAWN_NUM_JOBS]; + +static void spa_free(struct spa *spa) +{ + int i; + + if (!spa) + return; + + for (i = 0; i < spa->argc; i++) + free(spa->argv[i]); + free(spa->argv); + free(spa); +} + +static struct spa *spa_create(int argc, char *const argv[]) +{ + struct spa *spa; + int i; + + spa = calloc(1, sizeof(*spa)); + if (!spa) + return NULL; + spa->argc = argc; + spa->argv = malloc(argc * sizeof(char *)); + if (!spa->argv) + goto err; + for (i = 0; i < argc; i++) { + spa->argv[i] = strdup(argv[i]); + if (!spa->argv[i]) + goto err; + } + return spa; +err: + spa_free(spa); + return NULL; +} + +static void spawn_thread(void *arg) +{ + struct spa *spa = (struct spa *)arg; + ulong cycles = 0; + int repeatable = 0; + + job_ret[spa->job_idx] = cmd_process(0, spa->argc, spa->argv, + &repeatable, &cycles); + spa_free(spa); +} + +static unsigned int next_job_id(void) +{ + int i; + + for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++) + if (!job[i]) + return i + 1; + + /* No job available */ + return 0; +} + +static void refresh_jobs(void) +{ + int i; + + for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++) + if (job[i] && uthread_grp_done(job[i])) + job[i] = 0; + +} + +static int do_spawn(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned int id; + unsigned int idx; + struct spa *spa; + int ret; + + if (argc == 1) + return CMD_RET_USAGE; + + spa = spa_create(argc - 1, argv + 1); + if (!spa) + return CMD_RET_FAILURE; + + refresh_jobs(); + + id = next_job_id(); + if (!id) + return CMD_RET_FAILURE; + idx = id - 1; + + job[idx] = uthread_grp_new_id(); + + ret = uthread_create(NULL, spawn_thread, spa, 0, job[idx]); + if (ret) { + job[idx] = 0; + return CMD_RET_FAILURE; + } + + ret = env_set_ulong("job_id", id); + if (ret) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD(spawn, CONFIG_SYS_MAXARGS, 0, do_spawn, + "run commands and summarize execution time", + "command [args...]\n"); + +static enum command_ret_t wait_job(unsigned int idx) +{ + int prev = disable_ctrlc(false); + + while (!uthread_grp_done(job[idx])) { + if (ctrlc()) { + puts("<INTERRUPT>\n"); + disable_ctrlc(prev); + return CMD_RET_FAILURE; + } + uthread_schedule(); + } + + job[idx] = 0; + disable_ctrlc(prev); + + return job_ret[idx]; +} + +static int do_wait(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + enum command_ret_t ret = CMD_RET_SUCCESS; + unsigned long id; + unsigned int idx; + int i; + + if (argc == 1) { + for (i = 0; i < CONFIG_CMD_SPAWN_NUM_JOBS; i++) + if (job[idx]) + ret = wait_job(i); + } else { + for (i = 1; i < argc; i++) { + id = dectoul(argv[i], NULL); + if (id < 0 || id > CONFIG_CMD_SPAWN_NUM_JOBS) + return CMD_RET_USAGE; + idx = (int)id - 1; + ret = wait_job(idx); + } + } + + return ret; +} + +U_BOOT_CMD(wait, CONFIG_SYS_MAXARGS, 0, do_wait, + "wait for one or more jobs to complete", + "[job_id ...]\n" + " - Wait until all specified jobs have exited and return the\n" + " exit status of the last job waited for. When no job_id is\n" + " given, wait for all the background jobs.\n");
Add a spawn command which runs another command in the background, as well as a wait command to suspend the shell until one or more background jobs have completed. The job_id environment variable is set by spawn and wait accepts optional job ids, so that one can selectively wait on any job. Example: => date; spawn sleep 5; spawn sleep 3; date; echo "waiting..."; wait; date Date: 2025-02-21 (Friday) Time: 17:04:52 Date: 2025-02-21 (Friday) Time: 17:04:52 waiting... Date: 2025-02-21 (Friday) Time: 17:04:57 => Another example showing how background jobs can make initlizations faster. The board is i.MX93 EVK, with one spinning HDD connected to USB1 via a hub, and a network cable plugged into ENET1. # From power up / reset u-boot=> setenv autoload 0 u-boot=> setenv ud "usb start; dhcp" u-boot=> time run ud [...] time: 8.058 seconds # From power up / reset u-boot=> setenv autoload 0 u-boot=> setenv ud "spawn usb start; spawn dhcp; wait" u-boot=> time run ud [...] time: 4.475 seconds Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org> --- cmd/Kconfig | 17 +++++ cmd/Makefile | 2 + cmd/spawn.c | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 cmd/spawn.c