Adds the thread_set_affinity and thread_get_affinity RPCs.
The user facing API uses the processor_name_array_t data type for
consistency with the rest of GnuMach. The server side stores
affinities as bitmasks to allow for performant code in the
scheduler.
thread_set_affinity refuses an affinity mask that would end up
being entirely disabled.
The code touches CPU shutdown and reassignment: CPU removal is
refused if there is still some thread with an affinity mask that
would be entirely disabled.
---
include/mach/gnumach.defs | 8 ++
kern/machine.c | 77 +++++++++++++++++
kern/thread.c | 168 ++++++++++++++++++++++++++++++++++++++
kern/thread.h | 56 +++++++++++++
4 files changed, 309 insertions(+)
diff --git a/include/mach/gnumach.defs b/include/mach/gnumach.defs
index f5b2f7f2..9f98b47b 100644
--- a/include/mach/gnumach.defs
+++ b/include/mach/gnumach.defs
@@ -257,3 +257,11 @@ routine vm_get_size_limit(
map : vm_task_t;
out current_limit : vm_size_t;
out max_limit : vm_size_t);
+
+routine thread_set_affinity(
+ thread : thread_t;
+ processors_list : processor_name_array_t);
+
+routine thread_get_affinity(
+ thread : thread_t;
+ out processors_list : processor_name_array_t);
diff --git a/kern/machine.c b/kern/machine.c
index f1380367..e730434d 100644
--- a/kern/machine.c
+++ b/kern/machine.c
@@ -233,6 +233,64 @@ processor_request_action(
thread_wakeup((event_t)&action_queue);
}
+
+/*
+ * thread_would_hang() helper function for thread_shutdown and
+ * thread_assign operations.
+ *
+ * Refuse shutdown if it would strand any affinity-constrained thread in
+ * this processor set.
+ *
+ * Walk the pset thread queue and, for each thread whose affinity mask
+ * currently admits the processor being shut down, verify that some other
+ * processor in the same pset also satisfies that mask.
+ *
+ * If no alternate eligible processor exists, shutting this processor down
+ * would leave that thread without any runnable CPU in the pset, so abort
+ * the operation.
+ */
+static
+thread_t
+thread_would_hang(
+ processor_t processor)
+{
+ processor_set_t pset;
+ thread_t thread;
+ processor_t p;
+
+ pset = processor->processor_set;
+ pset_lock(pset);
+
+ queue_iterate(&pset->threads, thread, thread_t, pset_threads) {
+ thread_lock(thread);
+
+ if (thread->has_affinity &&
+ cpu_affinity_test(&thread->affinity, processor->slot_num)) {
+
+ queue_iterate(&pset->processors, p, processor_t, processors) {
+ if (p == processor)
+ continue;
+
+ if (cpu_affinity_test(&thread->affinity, p->slot_num)) {
+ goto thread_can_run;
+ }
+ }
+
+ thread_unlock(thread);
+ pset_unlock(pset);
+
+ return thread;
+ }
+
+thread_can_run:
+ thread_unlock(thread);
+ }
+
+ pset_unlock(pset);
+
+ return THREAD_NULL;
+}
+
#if MACH_HOST
/*
* processor_assign() changes the processor set that a processor is
@@ -256,6 +314,14 @@ processor_assign(
return(KERN_INVALID_ARGUMENT);
}
+ /*
+ * Refuse assignment if it would strand any affinity-constrained thread in
+ * this processor set.
+ *
+ */
+ if(thread_would_hang(processor) != THREAD_NULL)
+ return(KERN_FAILURE);
+
/*
* Get pset reference to donate to processor_request_action.
*/
@@ -362,6 +428,17 @@ processor_shutdown(processor_t processor)
return(KERN_SUCCESS);
}
+ /*
+ * Refuse shutdown if it would strand any affinity-constrained thread in
+ * this processor set.
+ *
+ */
+ if(thread_would_hang(processor) != THREAD_NULL) {
+ processor_unlock(processor);
+ splx(s);
+ return(KERN_FAILURE);
+ }
+
processor_request_action(processor, PROCESSOR_SET_NULL);
processor_unlock(processor);
splx(s);
diff --git a/kern/thread.c b/kern/thread.c
index deb9688d..f00304b2 100644
--- a/kern/thread.c
+++ b/kern/thread.c
@@ -367,6 +367,8 @@ void thread_init(void)
/* thread_template.processor_set (later) */
thread_template.bound_processor = PROCESSOR_NULL;
+ /* thread_template.has_affinity = FALSE; */
+ /* cpu_affinity_zero(&thread_template.affinity); */
#if MACH_HOST
thread_template.may_assign = TRUE;
thread_template.assign_active = FALSE;
@@ -451,6 +453,11 @@ kern_return_t thread_create(
pset_reference(pset);
task_unlock(parent_task);
+ /*
+ * Reset affinity
+ */
+ new_thread->has_affinity = FALSE;
+
/*
* This thread will mosty probably start working, assume it
* will take its share of CPU, to avoid having to find it out
@@ -2672,3 +2679,164 @@ thread_get_name(
return KERN_SUCCESS;
}
+
+/*
+ * thread_enforce_affinity
+ *
+ * Enforce the current thread affinity constraint.
+ *
+ * If the thread is active on a processor outside its allowed affinity set,
+ * trigger an AST on that processor so the thread is preempted and
+ * rescheduled onto a permitted CPU.
+ *
+ * Assumes the thread is locked and splsched is active.
+ */
+void
+thread_enforce_affinity(
+ thread_t thread)
+{
+ int i;
+ processor_t p;
+#if NCPUS > 1
+ if (thread->has_affinity == FALSE)
+ return;
+
+ for (i = 0; i < smp_get_numcpus(); i++) {
+ p = cpu_to_processor(i);
+
+ if (p == PROCESSOR_NULL || p->state == PROCESSOR_OFF_LINE)
+ continue;
+
+ if (percpu_array[i].active_thread == thread) {
+ if (!cpu_affinity_test(&thread->affinity, i))
+ cause_ast_check(p);
+ break;
+ }
+ }
+#endif
+}
+
+
+/*
+ *
+ * thread_set_affinity
+ *
+ * Set thread affinity, the procs array contains the target CPUs.
+ * A NULL array indicates to clear affinity and set default.
+ * Returns an error if any CPU is outside of the thread processor set.
+ *
+ * If the thread is currently executing, it may wait until its time slice
+ * is up before switching onto the specified processor.
+ *
+ */
+kern_return_t
+thread_set_affinity(
+ thread_t thread,
+ processor_name_array_t processor_list,
+ natural_t countp)
+{
+ cpu_affinity_t newmask;
+ processor_t p;
+ int i;
+ boolean_t any = FALSE;
+ spl_t s;
+
+ if (thread == THREAD_NULL)
+ return KERN_INVALID_ARGUMENT;
+
+ if (processor_list == NULL || countp == 0) {
+ thread->has_affinity = FALSE;
+ return KERN_SUCCESS;
+ }
+
+ cpu_affinity_zero(&newmask);
+
+ thread_lock(thread);
+ pset_lock(thread->processor_set);
+
+ for (i = 0; i < countp; i++) {
+ p = convert_port_to_processor_name(
+ (ipc_port_t)processor_list[i]);
+
+ if (p == PROCESSOR_NULL)
+ continue;
+
+ cpu_affinity_set(&newmask, (unsigned)p->slot_num);
+
+ /* reaquire at least one processor in the correct pset */
+ if (p->processor_set != thread->processor_set)
+ continue;
+
+ any = TRUE;
+ }
+
+ if (!any) {
+ pset_unlock(thread->processor_set);
+ thread_unlock(thread);
+ return KERN_INVALID_ARGUMENT; /* empty effective set */
+ }
+
+ thread->affinity = newmask;
+ thread->has_affinity = TRUE;
+
+ s = splsched();
+ thread_enforce_affinity(thread);
+ splx(s);
+
+ pset_unlock(thread->processor_set);
+ thread_unlock(thread);
+
+ return KERN_SUCCESS;
+}
+
+/*
+ * thread_get_affinity
+ *
+ * Return the thread affinity set. If thread doesn't have any affinity set,
+ * returns a void processor_list.
+ *
+ */
+kern_return_t
+thread_get_affinity(
+ thread_t thread,
+ processor_name_array_t *processor_list,
+ natural_t *countp)
+{
+ int i, n = 0;
+ natural_t count;
+ ipc_port_t *tp;
+
+ if (thread == THREAD_NULL)
+ return KERN_INVALID_ARGUMENT;
+
+ if (thread->has_affinity == FALSE) {
+ *processor_list = NULL;
+ *countp = 0;
+
+ return KERN_SUCCESS;
+ }
+
+ thread_lock(thread);
+
+ count = cpu_affinity_count(&thread->affinity);
+ assert(count <= NCPUS);
+
+ tp = (ipc_port_t *)kalloc(count * sizeof(ipc_port_t));
+ if (tp == NULL) {
+ thread_unlock(thread);
+ return KERN_RESOURCE_SHORTAGE;
+ }
+
+ for (i = 0; i < NCPUS; i++) {
+ if (cpu_affinity_test(&thread->affinity, i))
+ tp[n++] = convert_processor_name_to_port(
+ cpu_to_processor(i));
+ }
+
+ thread_unlock(thread);
+
+ *countp = count;
+ *processor_list = (mach_port_t *)tp;
+
+ return KERN_SUCCESS;
+}
diff --git a/kern/thread.h b/kern/thread.h
index e5128dad..2e5ceb47 100644
--- a/kern/thread.h
+++ b/kern/thread.h
@@ -34,6 +34,7 @@
#ifndef _KERN_THREAD_H_
#define _KERN_THREAD_H_
+#include <string.h>
#include <mach/boolean.h>
#include <mach/thread_info.h>
#include <mach/thread_status.h>
@@ -54,6 +55,48 @@
#include <machine/thread.h>
#include <ipc/ipc_kmsg_queue.h>
+typedef struct cpu_affinity {
+ uint32_t bits[(NCPUS + 31) / 32];
+} cpu_affinity_t;
+
+static inline void
+cpu_affinity_zero(cpu_affinity_t *m) {
+ memset(m->bits, 0, sizeof(m->bits));
+}
+
+static inline void
+cpu_affinity_set(cpu_affinity_t *m, unsigned cpu) {
+ m->bits[cpu >> 5] |= (1u << (cpu & 31));
+}
+
+static inline boolean_t
+cpu_affinity_test(const cpu_affinity_t *m, unsigned cpu) {
+ return (m->bits[cpu >> 5] & (1u << (cpu & 31))) != 0;
+}
+
+static inline unsigned int
+popcnt_uint32(uint32_t n) {
+ unsigned int count = 0;
+
+ while (n) {
+ n &= n - 1;
+ count++;
+ }
+
+ return count;
+}
+
+static inline natural_t
+cpu_affinity_count(const cpu_affinity_t *m) {
+ natural_t total = 0;
+ int i;
+
+ for (i = 0; i < sizeof(m->bits) / sizeof(m->bits[0]); i++)
+ total += popcnt_uint32(m->bits[i]);
+
+ return total;
+}
+
/*
* Thread name buffer size. Use the same size as the task so
* the thread can inherit the task's name.
@@ -223,6 +266,8 @@ struct thread {
/* Processor data structures */
processor_set_t processor_set; /* assigned processor set */
processor_t bound_processor; /* bound to processor ?*/
+ cpu_affinity_t affinity; /* cpu affinity mask */
+ boolean_t has_affinity; /* if FALSE, use default mask */
sample_control_t pc_sample;
@@ -360,6 +405,14 @@ extern kern_return_t thread_set_name(
extern kern_return_t thread_get_name(
thread_t thread,
kernel_debug_name_t name);
+extern kern_return_t thread_set_affinity(
+ thread_t thread,
+ processor_name_array_t processor_list,
+ natural_t countp);
+extern kern_return_t thread_get_affinity(
+ thread_t thread,
+ processor_name_array_t *processor_list,
+ natural_t *countp);
#endif
/*
@@ -387,6 +440,9 @@ extern thread_t kernel_thread(
extern void reaper_thread(void) __attribute__((noreturn));
+extern void thread_enforce_affinity(thread_t);
+
+
#if MACH_HOST
extern void thread_freeze(
thread_t thread);
--
2.53.0