This defines just the API, nothing is changed in the scheduler. I'd like to
get feedback on the choices I made.

Unfortunately, since in GNU/Hurd thread creation pass through the Task API,
and not the thread API, affinity inheritance will have to be implemented in
glibc.

Difference between v2 and v1: now allows to specify affinity for processors
outside of the pset, to mimick Linux behavior more closely. I am a bit
worried that the removal of all cpus in the affinity set would cause a
process to freeze. TODO: either reset all affinities of all thread in set
on processor removal, or assure that at least one cpu can run each thread
in pset on processor removal and if not reset affinity of that thread.

Happy to hear advices.
---
 include/mach/gnumach.defs |   8 ++
 kern/thread.c             | 176 ++++++++++++++++++++++++++++++++++++++
 kern/thread.h             |  45 ++++++++++
 3 files changed, 229 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/thread.c b/kern/thread.c
index deb9688d..05386d44 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.affinity_set = 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->affinity_set = 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,172 @@ 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->affinity_set == 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.
+ *
+ */
+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) {
+               s = splsched();
+               thread_lock(thread);
+               thread->affinity_set = FALSE;
+               cpu_affinity_zero(&thread->affinity);
+               thread_unlock(thread);
+               splx(s);
+               return KERN_SUCCESS;
+       }
+
+       cpu_affinity_zero(&newmask);
+
+       s = splsched();
+       thread_lock(thread);
+
+       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);
+
+               /* require at least one processor online */
+               if (p->state == PROCESSOR_OFF_LINE)
+                       continue;
+
+               /* and in the correct pset membership */
+               if (p->processor_set != thread->processor_set)
+                       continue;
+
+               any = TRUE;
+       }
+
+       if (!any) {
+               thread_unlock(thread);
+               splx(s);
+               return KERN_INVALID_ARGUMENT; /* empty effective set */
+       }
+
+       thread->affinity = newmask;
+       thread->affinity_set = TRUE;
+       thread_enforce_affinity();
+
+       thread_unlock(thread);
+       splx(s);
+
+       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;
+       spl_t                   s;
+
+       if (thread == THREAD_NULL)
+               return KERN_INVALID_ARGUMENT;
+
+       s = splsched();
+       thread_lock(thread);
+
+       if (thread->affinity_set == FALSE) {
+               thread_unlock(thread);
+               splx(s);
+               *processor_list = NULL;
+               *countp = 0;
+
+               return KERN_SUCCESS;
+       }
+
+       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);
+               splx(s);
+
+               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);
+       splx(s);
+
+       *countp = count;
+       *processor_list = (mach_port_t *)tp;
+
+       return KERN_SUCCESS;
+}
diff --git a/kern/thread.h b/kern/thread.h
index e5128dad..b525c7f6 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       affinity_set;   /* if FALSE, use default mask */
 
        sample_control_t pc_sample;
 
-- 
2.53.0


Reply via email to