[PATCH] util-linux/taskset: add support for taking/printing CPU list (-c option)

Denys Vlasenko vda.linux at googlemail.com
Fri Nov 1 14:48:09 UTC 2019


Applied a modified version, please try current git.

On Wed, Oct 30, 2019 at 6:55 PM Fryderyk Wrobel <frd1996 at gmail.com> wrote:
>
> Hi,
>
> This patch implements '-c' option in taskset. With this option taskset
> will
> print or take the affinity as CPUs list, for example: "1,3,5-7".
>
> Limitations:
>   * Pattern specifiers after a range (e.g. "0-255:2/64") are not
> supported.
>   * Leading/trailing white-spaces are not allowed, e.g. list " 1,2 " will fail.
>
> Kind Regards,
> Fryderyk
>
> taskset: make CPU list support an optional feature
>
> function                                             old     new   delta
> taskset_main                                         598    1035    +437
> .rodata                                           158112  158151     +39
> packed_usage                                       33417   33419      +2
> ------------------------------------------------------------------------------
> (add/remove: 0/0 grow/shrink: 3/0 up/down: 478/0)             Total: 478 bytes
>
> diff --git a/util-linux/taskset.c b/util-linux/taskset.c
> index ed8878ad4..c12728802 100644
> --- a/util-linux/taskset.c
> +++ b/util-linux/taskset.c
> @@ -20,16 +20,29 @@
>  //config:      Needed for machines with more than 32-64 CPUs:
>  //config:      affinity parameter 0xHHHHHHHHHHHHHHHHHHHH can be arbitrarily long
>  //config:      in this case. Otherwise, it is limited to sizeof(long).
> +//config:
> +//config:config FEATURE_TASKSET_CPULIST
> +//config:      bool "CPU list support (-c option)"
> +//config:      default n
> +//config:      depends on TASKSET
> +//config:      help
> +//config:      Add support for taking/printing affinity as CPU list when '-c'
> +//config:      option is used. For example, it prints '0-3,7' instead of mask '8f'.
> +//config:
> +
>
>  //applet:IF_TASKSET(APPLET_NOEXEC(taskset, taskset, BB_DIR_USR_BIN,
> BB_SUID_DROP, taskset))
>
>  //kbuild:lib-$(CONFIG_TASKSET) += taskset.o
>
>  //usage:#define taskset_trivial_usage
> -//usage:       "[-p] [HEXMASK] PID | PROG ARGS"
> +//usage:       IF_FEATURE_TASKSET_CPULIST("[-c] ") "[-p] [HEXMASK"
> IF_FEATURE_TASKSET_CPULIST("|LIST") "] PID | PROG ARGS"
>  //usage:#define taskset_full_usage "\n\n"
>  //usage:       "Set or get CPU affinity\n"
>  //usage:     "\n       -p      Operate on an existing PID"
> +//usage:       IF_FEATURE_TASKSET_CPULIST(
> +//usage:     "\n       -c      Display and specify CPUs in list format"
> +//usage:       )
>  //usage:
>  //usage:#define taskset_example_usage
>  //usage:       "$ taskset 0x7 ./dgemm_test&\n"
> @@ -45,7 +58,6 @@
>   * Not yet implemented:
>   * -a/--all-tasks (affect all threads)
>   *     needs to get TIDs from /proc/PID/task/ and use _them_ as "pid" in
> sched_setaffinity(pid)
> - * -c/--cpu-list  (specify CPUs via "1,3,5-7")
>   */
>
>  #include <sched.h>
> @@ -87,6 +99,117 @@ static unsigned long long from_mask(ul *mask,
> unsigned sz_in_bytes UNUSED_PARAM)
>  }
>  #endif
>
> +#if ENABLE_FEATURE_TASKSET_CPULIST
> +
> +/*
> + * Parse the CPU list and set the mask accordingly.
> + *
> + * The list element can be either a CPU index or a range of CPU indices.
> + * Example: "1,3,5-7".
> + *
> + * note1: pattern specifiers after a range (e.g. 0-255:2/64) are not supported
> + * note2: leading/trailing white-spaces are not allowed
> + */
> +static int parse_cpulist(ul *mask, unsigned max, const char *s)
> +{
> +       enum parser_state {
> +               RDY,     /* Ready */
> +               LD0,     /* Loading first number */
> +               LD1      /* Loading second number */
> +       };
> +       enum parser_state state = RDY;
> +       unsigned v[2] = { 0, 0 };
> +       unsigned i;
> +       char c;
> +
> +       /* Nesting switch statements is not the prettiest thing in the
> +        * world but this one produces a bit shorter code vs if/else
> +        * when compiled with -O2/-O3 on x86_64 */
> +       do {
> +               c = *s++;
> +
> +               switch (c) {
> +               case '0' ... '9':
> +                       /* Append a digit to the current number */
> +                       switch (state) {
> +                       case RDY:
> +                               v[0] = v[1] = 0;
> +                               state = LD0;
> +                               /* fall through */
> +                       case LD0:
> +                               v[0] = 10 * v[0] + c - '0';
> +                               break;
> +                       case LD1:
> +                               v[1] = 10 * v[1] + c - '0';
> +                               break;
> +                       }
> +                       break;
> +
> +               case '-':
> +                       /* It may be a range */
> +                       if (state != LD0)
> +                               return -1;
> +                       state = LD1;                  /* Move to the second number */
> +                       break;
> +
> +               case ',':
> +               case '\0':
> +                       /* End of number/range */
> +                       switch (state) {
> +                       case LD0:                     /* Have a single number loaded */
> +                               v[1] = v[0];          /* Make it a range: N-N */
> +                               /* fall through */
> +                       case LD1:                     /* Have two numbers loaded */
> +                               if (v[1] < v[0])
> +                                       return -1;    /* Bad range, e.g. 3-1 */
> +
> +                               for (i = v[0]; i <= v[1] && i < max; i++)
> +                                       mask[i / BITS_UL] |= (1UL << (i & MASK_UL));
> +
> +                               state = RDY;          /* Try to load next number/range  */
> +                               break;
> +                       default:
> +                               return -1;
> +                       }
> +                       break;
> +
> +               default:
> +                       return -1;
> +               }
> +       } while (c != '\0');
> +
> +       return 0;
> +}
> +
> +static void print_cpulist(const ul *mask, unsigned max)
> +{
> +       const char *delim = "";
> +       unsigned i, j;
> +
> +#define MASK_ISSET(m, b)  (m[(b) / BITS_UL] & (1UL << ((b) & MASK_UL)))
> +
> +       for (i = 0; i < max; i++) {
> +               if (MASK_ISSET(mask, i)) {
> +                       for (j = i + 1; j < max && MASK_ISSET(mask, j); j++) {}
> +                       j--;
> +
> +                       if (i == j) {
> +                               printf("%s%u", delim, i);
> +                       } else {
> +                               printf("%s%u-%u", delim, i, j);
> +                               i = j;
> +                       }
> +
> +                       delim=",";
> +               }
> +       }
> +
> +       putchar('\n');
> +#undef MASK_ISSET
> +}
> +
> +#endif /* ENABLE_FEATURE_TASKSET_CPULIST */
> +
>  static unsigned long *get_aff(int pid, unsigned *sz)
>  {
>         int r;
> @@ -114,20 +237,22 @@ int taskset_main(int argc UNUSED_PARAM, char **argv)
>         ul *mask;
>         unsigned mask_size_in_bytes;
>         pid_t pid = 0;
> -       unsigned opt_p;
> +       unsigned opts;
>         const char *current_new;
>         char *aff;
>
> +       enum { OPT_p = 1, OPT_c = 2 };
> +
>         /* NB: we mimic util-linux's taskset: -p does not take
>          * an argument, i.e., "-pN" is NOT valid, only "-p N"!
>          * Indeed, util-linux-2.13-pre7 uses:
>          * getopt_long(argc, argv, "+pchV", ...), not "...p:..." */
>
> -       opt_p = getopt32(argv, "^+" "p" "\0" "-1" /* at least 1 arg */);
> +       opts = getopt32(argv, "^+" "p" IF_FEATURE_TASKSET_CPULIST("c") "\0"
> "-1" /* at least 1 arg */);
>         argv += optind;
>
>         aff = *argv++;
> -       if (opt_p) {
> +       if (opts & OPT_p) {
>                 char *pid_str = aff;
>                 if (*argv) { /* "-p <aff> <pid> ...rest.is.ignored..." */
>                         pid_str = *argv; /* NB: *argv != NULL in this case */
> @@ -144,9 +269,16 @@ int taskset_main(int argc UNUSED_PARAM, char **argv)
>         current_new = "current";
>   print_aff:
>         mask = get_aff(pid, &mask_size_in_bytes);
> -       if (opt_p) {
> -               printf("pid %d's %s affinity mask: "TASKSET_PRINTF_MASK"\n",
> -                               pid, current_new, from_mask(mask, mask_size_in_bytes));
> +       if (opts & OPT_p) {
> +#if ENABLE_FEATURE_TASKSET_CPULIST
> +               if (opts & OPT_c) {
> +                       printf("pid %d's %s affinity list: ", pid, current_new);
> +                       print_cpulist(mask, mask_size_in_bytes * 8);
> +               } else
> +#endif
> +                       printf("pid %d's %s affinity mask: " TASKSET_PRINTF_MASK"\n",
> +                                       pid, current_new, from_mask(mask, mask_size_in_bytes));
> +
>                 if (*argv == NULL) {
>                         /* Either it was just "-p <pid>",
>                          * or it was "-p <aff> <pid>" and we came here
> @@ -158,55 +290,67 @@ int taskset_main(int argc UNUSED_PARAM, char **argv)
>         }
>         memset(mask, 0, mask_size_in_bytes);
>
> -       /* Affinity was specified, translate it into mask */
> -       /* it is always in hex, skip "0x" if it exists */
> -       if (aff[0] == '0' && (aff[1]|0x20) == 'x')
> -               aff += 2;
> -
> -       if (!ENABLE_FEATURE_TASKSET_FANCY) {
> -               mask[0] = xstrtoul(aff, 16);
> +#if ENABLE_FEATURE_TASKSET_CPULIST
> +       if (opts & OPT_c) {
> +               /* Cpulist */
> +               if (parse_cpulist(mask, mask_size_in_bytes * 8, aff) < 0)
> +                       bb_error_msg_and_die("bad affinity '%s'", aff);
>         } else {
> -               unsigned i;
> -               char *last_char;
> -
> -               i = 0; /* bit pos in mask[] */
> -
> -               /* aff is ASCII hex string, accept very long masks in this form.
> -                * Process hex string AABBCCDD... to ulong mask[]
> -                * from the rightmost nibble, which is least-significant.
> -                * Bits not fitting into mask[] are ignored: (example: 1234
> -                * in 12340000000000000000000000000000000000000ff)
> -                */
> -               last_char = strchrnul(aff, '\0');
> -               while (last_char > aff) {
> -                       char c;
> -                       ul val;
> -
> -                       last_char--;
> -                       c = *last_char;
> -                       if (isdigit(c))
> -                               val = c - '0';
> -                       else if ((c|0x20) >= 'a' && (c|0x20) <= 'f')
> -                               val = (c|0x20) - ('a' - 10);
> -                       else
> -                               bb_error_msg_and_die("bad affinity '%s'", aff);
> -
> -                       if (i < mask_size_in_bytes * 8) {
> -                               mask[i / BITS_UL] |= val << (i & MASK_UL);
> -                               //bb_error_msg("bit %d set", i);
> -                       }
> -                       /* else:
> -                        * We can error out here, but we don't.
> -                        * For one, kernel itself ignores bits in mask[]
> -                        * which do not map to any CPUs:
> -                        * if mask[] has one 32-bit long element,
> -                        * but you have only 8 CPUs, all bits beyond first 8
> -                        * are ignored, silently.
> -                        * No point in making bits past 31th to be errors.
> +#endif
> +               /* Bitmask */
> +
> +               /* Affinity was specified, translate it into mask */
> +               /* it is always in hex, skip "0x" if it exists */
> +               if (aff[0] == '0' && (aff[1]|0x20) == 'x')
> +                       aff += 2;
> +
> +               if (!ENABLE_FEATURE_TASKSET_FANCY) {
> +                       mask[0] = xstrtoul(aff, 16);
> +               } else {
> +                       unsigned i;
> +                       char *last_char;
> +
> +                       i = 0; /* bit pos in mask[] */
> +
> +                       /* aff is ASCII hex string, accept very long masks in this form.
> +                        * Process hex string AABBCCDD... to ulong mask[]
> +                        * from the rightmost nibble, which is least-significant.
> +                        * Bits not fitting into mask[] are ignored: (example: 1234
> +                        * in 12340000000000000000000000000000000000000ff)
>                          */
> -                       i += 4;
> +                       last_char = strchrnul(aff, '\0');
> +                       while (last_char > aff) {
> +                               char c;
> +                               ul val;
> +
> +                               last_char--;
> +                               c = *last_char;
> +                               if (isdigit(c))
> +                                       val = c - '0';
> +                               else if ((c|0x20) >= 'a' && (c|0x20) <= 'f')
> +                                       val = (c|0x20) - ('a' - 10);
> +                               else
> +                                       bb_error_msg_and_die("bad affinity '%s'", aff);
> +
> +                               if (i < mask_size_in_bytes * 8) {
> +                                       mask[i / BITS_UL] |= val << (i & MASK_UL);
> +                                       //bb_error_msg("bit %d set", i);
> +                               }
> +                               /* else:
> +                                * We can error out here, but we don't.
> +                                * For one, kernel itself ignores bits in mask[]
> +                                * which do not map to any CPUs:
> +                                * if mask[] has one 32-bit long element,
> +                                * but you have only 8 CPUs, all bits beyond first 8
> +                                * are ignored, silently.
> +                                * No point in making bits past 31th to be errors.
> +                                */
> +                               i += 4;
> +                       }
>                 }
> +#if ENABLE_FEATURE_TASKSET_CPULIST
>         }
> +#endif
>
>         /* Set pid's or our own (pid==0) affinity */
>         if (sched_setaffinity(pid, mask_size_in_bytes, (void*)mask))
> _______________________________________________
> busybox mailing list
> busybox at busybox.net
> http://lists.busybox.net/mailman/listinfo/busybox


More information about the busybox mailing list