[Toybox] [PATCH] taskset: Add -c cpulist support
Jesse Rosenstock
jmr at google.com
Sun Sep 28 11:09:55 PDT 2025
`taskset -c` is supported by util-linux as a user-friendly way of
specifying the affinity mask.
https://man7.org/linux/man-pages/man1/taskset.1.html#OPTIONS
---
lib/lib.c | 7 +-
tests/taskset.test | 31 ++++++++++++
toys/other/taskset.c | 130
+++++++++++++++++++++++++++++++++++++++++++----------
3 files changed, 144 insertions(+), 24 deletions(-)
diff --git a/lib/lib.c b/lib/lib.c
index 7ae3ff66f0..b9d1f88b4e 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -293,7 +293,12 @@
{
errno = 0;
- return strtoll(str, end, base);
+ long long l = strtoll(str, end, base);
+ // POSIX says strtoll *may* set errno to EINVAL when no input is consumed.
+ // https://pubs.opengroup.org/onlinepubs/9799919799/functions/strtol.html
+ // Set it consistently.
+ if (str == *end) errno = EINVAL;
+ return l;
}
long long xstrtol(char *str, char **end, int base)
diff --git a/tests/taskset.test b/tests/taskset.test
index 5732e23f0e..33e751b450 100755
--- a/tests/taskset.test
+++ b/tests/taskset.test
@@ -22,6 +22,37 @@
testing 'set mask to last' \
'(taskset $LAST taskset -p 0 | sed "s/.*: //")' "$LAST\n" '' ''
+# mask -> CPU list
+if [ $CPUS -gt 1 ]; then
+ testing 'print all as CPU list' \
+ '(taskset $MASK taskset -pc 0 | sed "s/.*: //")' "0-$((CPUS-1))\n" '' ''
+fi
+testing 'print first as CPU list' \
+ '(taskset 1 taskset -pc 0 | sed "s/.*: //")' '0\n' '' ''
+testing 'print last as CPU list' \
+ '(taskset $LAST taskset -pc 0 | sed "s/.*: //")' "$((CPUS-1))\n" '' ''
+
+# CPU list -> mask
+testing 'set CPU list to all' \
+ '(taskset -c 0-$((CPUS-1)) taskset -p 0 | sed "s/.*: //")' "$MASK\n" '' ''
+testing 'set CPU list to first' \
+ '(taskset -c 0 taskset -p 0 | sed "s/.*: //")' '1\n' '' ''
+testing 'set CPU list to last' \
+ '(taskset -c $((CPUS-1)) taskset -p 0 | sed "s/.*: //")' "$LAST\n" '' ''
+
+# CPU list -> CPU list
+if [ $CPUS -gt 2 ]; then
+ testing 'even CPU list' \
+ '(taskset -c $(seq -s, 0 2 $CPUS) taskset -pc 0 | sed "s/.*: //")' \
+ "0-$((($CPUS-1)&~1)):2\n" '' ''
+fi
+if [ $CPUS -gt 5 ]; then
+ # Prints as 0,1-5:2, not 0,1,3,5, since 1 not consumed as part of a
+ # range starting at 0.
+ testing 'CPU list printing not greedy' \
+ '(taskset -c 0,1-5:2 taskset -pc 0 | sed "s/.*: //")' '0,1-5:2\n' '' ''
+fi
+
# alas procps-ng always says "-" for -o cpu so fetch the field from /proc
testing 'run on first' \
'X=$(taskset 1 cat /proc/self/stat); x() { echo ${39};}; x $X' \
diff --git a/toys/other/taskset.c b/toys/other/taskset.c
index 94044ebd07..d4b1f1bb87 100644
--- a/toys/other/taskset.c
+++ b/toys/other/taskset.c
@@ -2,7 +2,7 @@
*
* Copyright 2012 Elie De Brauwer <eliedebrauwer at gmail.com>
-USE_TASKSET(NEWTOY(taskset, "^pa", TOYFLAG_USR|TOYFLAG_BIN))
+USE_TASKSET(NEWTOY(taskset, "^pac", TOYFLAG_USR|TOYFLAG_BIN))
USE_NPROC(NEWTOY(nproc, "a(all)", TOYFLAG_USR|TOYFLAG_BIN))
config NPROC
@@ -19,17 +19,19 @@
bool "taskset"
default y
help
- usage: taskset [-ap] [mask] [PID | cmd [args...]]
+ usage: taskset [-apc] [mask] [PID | cmd [args...]]
Launch a new task which may only run on certain processors, or change
the processor affinity of an existing PID.
- Mask is a hex string where each bit represents a processor the process
- is allowed to run on. PID without a mask displays existing affinity.
- A PID of zero means the taskset process.
+ The mask may be specified as a hex string where each bit represents a
+ processor the process is allowed to run on, or as a CPU list with the
+ -c option. PID without a mask displays existing affinity. A PID of zero
+ means the taskset process.
-p Set/get affinity of given PID instead of a new command
-a Set/get affinity of all threads of the PID
+ -c Specify mask as a cpu list, for example 1,3,4-8:2
*/
#define FOR_taskset
@@ -41,6 +43,16 @@
#define sched_getaffinity(pid, size, cpuset) \
syscall(__NR_sched_getaffinity, (pid_t)pid, (size_t)size, (void *)cpuset)
+#define TOYBUF_BITS (8*sizeof(toybuf))
+
+static int find_next_cpu(unsigned long *mask, int i)
+{
+ for (; i < TOYBUF_BITS; i++)
+ if (mask[i/(8*sizeof(long))] & (1UL << (i%(8*sizeof(long)))))
+ return i;
+ return i;
+}
+
static void do_taskset(pid_t pid)
{
unsigned long *mask = (unsigned long *)toybuf;
@@ -56,11 +68,43 @@
if (toys.optc)
printf("pid %d's %s affinity mask: ", pid, i ? "new" : "current");
- for (j = sizeof(toybuf)/sizeof(long), k = 0; --j>=0;) {
- if (k) printf("%0*lx", (int)(2*sizeof(long)), mask[j]);
- else if (mask[j]) {
- k++;
- printf("%lx", mask[j]);
+ if (FLAG(c)) {
+ // Print as cpu list
+ int next = 0; // where to start looking for next cpu
+ int a; // start of range
+
+ while ((a = find_next_cpu(mask, next)) < TOYBUF_BITS) {
+ // next is 0 only on first iteration.
+ if (next > 0) putchar(',');
+ next = find_next_cpu(mask, a + 1);
+ if (next == TOYBUF_BITS) {
+ printf("%d", a);
+ } else {
+ // Extend range as long as the step size is the same.
+ int b = next, step = b - a;
+ while ((next = find_next_cpu(mask, b + 1)) < TOYBUF_BITS
+ && next - b == step) {
+ b = next;
+ }
+ if (b - a == step) {
+ // Only print one CPU; try to put the next CPU in the next range.
+ printf("%d", a);
+ next = b;
+ } else if (step == 1) {
+ printf("%d-%d", a, b);
+ } else {
+ printf("%d-%d:%d", a, b, step);
+ }
+ }
+ }
+ } else {
+ // Print as mask
+ for (j = sizeof(toybuf)/sizeof(long), k = 0; --j>=0;) {
+ if (k) printf("%0*lx", (int)(2*sizeof(long)), mask[j]);
+ else if (mask[j]) {
+ k++;
+ printf("%lx", mask[j]);
+ }
}
}
putchar('\n');
@@ -68,20 +112,60 @@
if (i || toys.optc < 2) return;
- // Convert hex string to mask[] bits
memset(toybuf, 0, sizeof(toybuf));
- j = (k = strlen(s = *toys.optargs))-2*sizeof(toybuf);
- if (j>0) {
- s += j;
- k -= j;
- }
- s += k;
- for (j = 0; j<k; j++) {
- unsigned long digit = *(--s) - '0';
-
- if (digit > 9) digit = 10 + tolower(*s)-'a';
- if (digit > 15) error_exit("bad mask '%s'", *toys.optargs);
- mask[j/(2*sizeof(long))] |= digit << 4*(j&((2*sizeof(long))-1));
+ if (FLAG(c)) {
+ // Convert cpu list to mask[] bits
+ char* failed_cpu_list = "failed to parse CPU list: %s";
+ s = *toys.optargs;
+ do {
+ if (s != *toys.optargs) {
+ if (*s != ',') {
+ error_exit(failed_cpu_list, *toys.optargs);
+ }
+ s++;
+ }
+
+ // Parse a[-b[:step]].
+ long long a = estrtol(s, &s, 10);
+ if (errno || a < 0) error_exit(failed_cpu_list, *toys.optargs);
+
+ long long b = a, step = 1;
+ if (*s == '-') {
+ s++;
+ b = estrtol(s, &s, 10);
+ if (errno || b >= TOYBUF_BITS || b < a) {
+ error_exit(failed_cpu_list, *toys.optargs);
+ }
+
+ if (*s == ':') {
+ s++;
+ step = estrtol(s, &s, 10);
+ // Reject large steps to avoid overflow in loop below.
+ if (errno || step < 1 || step >= TOYBUF_BITS) {
+ error_exit(failed_cpu_list, *toys.optargs);
+ }
+ }
+ }
+
+ for (j = a; j <= b; j += step) {
+ mask[j/(8*sizeof(long))] |= 1UL << (j%(8*sizeof(long)));
+ }
+ } while (*s);
+ } else {
+ // Convert hex string to mask[] bits.
+ j = (k = strlen(s = *toys.optargs))-2*sizeof(toybuf);
+ if (j>0) {
+ s += j;
+ k -= j;
+ }
+ s += k;
+ for (j = 0; j<k; j++) {
+ unsigned long digit = *(--s) - '0';
+
+ if (digit > 9) digit = 10 + tolower(*s)-'a';
+ if (digit > 15) error_exit("bad mask '%s'", *toys.optargs);
+ mask[j/(2*sizeof(long))] |= digit << 4*(j&((2*sizeof(long))-1));
+ }
}
if (-1 == sched_setaffinity(pid, sizeof(toybuf), (void *)mask))
-------------- next part --------------
A non-text attachment was scrubbed...
Name: taskset-cpu-list.patch
Type: text/x-patch
Size: 7905 bytes
Desc: not available
URL: <http://lists.landley.net/pipermail/toybox-landley.net/attachments/20250928/6ff2388a/attachment-0001.bin>
More information about the Toybox
mailing list