[PATCH v2] Cygwin: Implement sched_[gs]etaffinity()

Mark Geisert mark@maxrnd.com
Mon Apr 29 05:38:00 GMT 2019


There are a couple of multi-group affinity operations that cannot be done
without heroic measures.  Those are marked with XXX in the code.  Further
discussion would be helpful to me.

---
 newlib/libc/include/sched.h     |  13 ++
 winsup/cygwin/common.din        |   4 +
 winsup/cygwin/include/pthread.h |   2 +
 winsup/cygwin/sched.cc          | 237 ++++++++++++++++++++++++++++++++
 winsup/cygwin/thread.cc         |  19 +++
 5 files changed, 275 insertions(+)

diff --git a/newlib/libc/include/sched.h b/newlib/libc/include/sched.h
index 1016235bb..a4d3fea6a 100644
--- a/newlib/libc/include/sched.h
+++ b/newlib/libc/include/sched.h
@@ -92,6 +92,19 @@ int sched_yield( void );
 
 #if __GNU_VISIBLE
 int sched_getcpu(void);
+
+#ifdef __CYGWIN__
+/* Affinity-related definitions, here until numerous enough to separate out */
+typedef uint64_t cpu_set_t;
+#define CPU_SETSIZE 1024
+#define CPU_GROUPSIZE 64
+#define CPU_GROUPMAX (CPU_SETSIZE / CPU_GROUPSIZE)
+
+int sched_getaffinity (pid_t, size_t, cpu_set_t *);
+int sched_get_thread_affinity (void *, size_t, cpu_set_t *);
+int sched_setaffinity (pid_t, size_t, const cpu_set_t *);
+int sched_set_thread_affinity (void *, size_t, const cpu_set_t *);
+#endif
 #endif
 
 #ifdef __cplusplus
diff --git a/winsup/cygwin/common.din b/winsup/cygwin/common.din
index 68b95d470..81292ab7b 100644
--- a/winsup/cygwin/common.din
+++ b/winsup/cygwin/common.din
@@ -1084,6 +1084,7 @@ pthread_create SIGFE
 pthread_detach SIGFE
 pthread_equal SIGFE
 pthread_exit SIGFE
+pthread_getaffinity_np SIGFE
 pthread_getattr_np SIGFE
 pthread_getconcurrency SIGFE
 pthread_getcpuclockid SIGFE
@@ -1128,6 +1129,7 @@ pthread_rwlockattr_getpshared SIGFE
 pthread_rwlockattr_init SIGFE
 pthread_rwlockattr_setpshared SIGFE
 pthread_self SIGFE
+pthread_setaffinity_np SIGFE
 pthread_setcancelstate SIGFE
 pthread_setcanceltype SIGFE
 pthread_setconcurrency SIGFE
@@ -1248,10 +1250,12 @@ scandirat SIGFE
 scanf SIGFE
 sched_get_priority_max SIGFE
 sched_get_priority_min SIGFE
+sched_getaffinity SIGFE
 sched_getcpu SIGFE
 sched_getparam SIGFE
 sched_getscheduler NOSIGFE
 sched_rr_get_interval SIGFE
+sched_setaffinity SIGFE
 sched_setparam SIGFE
 sched_setscheduler SIGFE
 sched_yield SIGFE
diff --git a/winsup/cygwin/include/pthread.h b/winsup/cygwin/include/pthread.h
index 2ccf1cf8b..4ef3aeab7 100644
--- a/winsup/cygwin/include/pthread.h
+++ b/winsup/cygwin/include/pthread.h
@@ -226,8 +226,10 @@ void pthread_testcancel (void);
 /* Non posix calls */
 
 #if __GNU_VISIBLE
+int pthread_getaffinity_np (pthread_t, size_t, cpu_set_t *);
 int pthread_getattr_np (pthread_t, pthread_attr_t *);
 int pthread_getname_np (pthread_t, char *, size_t) __attribute__((__nonnull__(2)));
+int pthread_setaffinity_np (pthread_t, size_t, const cpu_set_t *);
 int pthread_setname_np (pthread_t, const char *) __attribute__((__nonnull__(2)));
 int pthread_sigqueue (pthread_t *, int, const union sigval);
 int pthread_timedjoin_np (pthread_t, void **, const struct timespec *);
diff --git a/winsup/cygwin/sched.cc b/winsup/cygwin/sched.cc
index 10168e641..2d527da69 100644
--- a/winsup/cygwin/sched.cc
+++ b/winsup/cygwin/sched.cc
@@ -424,4 +424,241 @@ sched_getcpu ()
   return pnum.Group * __get_cpus_per_group () + pnum.Number;
 }
 
+/* figure out which processor group the set bits indicate; can only be one */
+static int
+whichgroup (size_t sizeof_set, const cpu_set_t *set)
+{
+  //XXX code assumes __get_cpus_per_group() is fixed at 64
+  int res = -1;
+
+  for (unsigned int i = 0; i < sizeof_set / sizeof (cpu_set_t); ++i)
+    if (set[i])
+      {
+	if (res >= 0)
+	  return -1; // error return if more than one group indicated
+	else
+	  res = (int) i; // remember first group found
+      }
+
+  return res;
+}
+
+int
+sched_get_thread_affinity (HANDLE thread, size_t sizeof_set, cpu_set_t *set)
+{
+  int status = 0;
+
+  //XXX code assumes __get_cpus_per_group() is fixed at 64
+  if (thread)
+    {
+      memset (set, 0, sizeof_set);
+      if (wincap.has_processor_groups ())
+	{
+	  GROUP_AFFINITY ga;
+
+	  if (!GetThreadGroupAffinity (thread, &ga))
+	    {
+	      status = geterrno_from_win_error (GetLastError (), EPERM);
+	      goto done;
+	    }
+	  set[ga.Group] = ga.Mask;
+	}
+      else
+	{
+	  // There is no GetThreadAffinityMask() function, so simulate one by
+	  // iterating through CPUs trying to set affinity, which returns the
+	  // previous affinity.  On success, restore original affinity.
+	  // This strategy is due to Damon on StackOverflow.
+	  KAFFINITY cpumask = 1;
+	  KAFFINITY oldmask = 0;
+
+	  // Iterate through CPUs until success setting thread affinity to it
+	  while (cpumask)
+	    {
+	      oldmask = SetThreadAffinityMask (thread, cpumask);
+	      if (oldmask)
+		{ // that one worked, so restore original mask
+		  SetThreadAffinityMask (thread, oldmask);
+		  set[0] = oldmask;
+		  goto done;
+		}
+	      if (GetLastError () != ERROR_INVALID_PARAMETER)
+		{ // that one failed in an unexpected way
+		  status = geterrno_from_win_error (GetLastError (), EPERM);
+		  goto done;
+		}
+	      cpumask <<= 1;
+	    }
+	  status = ENOSYS; //XXX strategy failed.. figure out a new one
+	}
+    }
+  else
+    status = ESRCH;
+
+done:
+  return status;
+}
+
+int
+sched_getaffinity (pid_t pid, size_t sizeof_set, cpu_set_t *set)
+{
+  HANDLE process = 0;
+  int status = 0;
+
+  //XXX code assumes __get_cpus_per_group() is fixed at 64
+  pinfo p (pid ? pid : getpid ());
+  if (p)
+    {
+      process = pid && pid != myself->pid ?
+                OpenProcess (PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
+                             p->dwProcessId) : GetCurrentProcess ();
+      KAFFINITY procmask;
+      KAFFINITY sysmask;
+
+      if (!GetProcessAffinityMask (process, &procmask, &sysmask))
+        {
+oops:
+          status = geterrno_from_win_error (GetLastError (), EPERM);
+          goto done;
+        }
+      memset (set, 0, sizeof_set);
+      if (wincap.has_processor_groups ())
+        {
+          USHORT groupcount = CPU_GROUPMAX;
+          USHORT grouparray[CPU_GROUPMAX];
+
+          if (!GetProcessGroupAffinity (process, &groupcount, grouparray))
+            goto oops;
+          if (groupcount == 1)
+	    set[grouparray[0]] = procmask;
+	  else
+            status = ENOSYS;//XXX multi-group code TBD...
+	    // There is no way to assemble the complete process affinity mask
+	    // without querying at least one thread per group in grouparray,
+	    // and we don't know which group a thread is in without querying
+	    // it, so must query all threads.  I'd call that a heroic measure.
+        }
+      else
+        set[0] = procmask;
+    }
+  else
+    status = ESRCH;
+
+done:
+  if (process && process != GetCurrentProcess ())
+    CloseHandle (process);
+
+  return status;
+}
+
+int
+sched_set_thread_affinity (HANDLE thread, size_t sizeof_set, const cpu_set_t *set)
+{
+  int group = whichgroup (sizeof_set, set);
+  int status = 0;
+
+  //XXX code assumes __get_cpus_per_group() is fixed at 64
+  if (thread)
+    {
+      if (wincap.has_processor_groups ())
+	{
+	  GROUP_AFFINITY ga;
+
+	  if (group < 0)
+	    {
+	      status = EINVAL;
+	      goto done;
+	    }
+	  memset (&ga, 0, sizeof (ga));
+	  ga.Mask = set[group];
+	  ga.Group = group;
+	  if (!SetThreadGroupAffinity (thread, &ga, NULL))
+	    {
+	      status = geterrno_from_win_error (GetLastError (), EPERM);
+	      goto done;
+	    }
+	}
+      else
+	{
+	  if (group != 0)
+	    {
+	      status = EINVAL;
+	      goto done;
+	    }
+	  if (!SetThreadAffinityMask (thread, set[0]))
+	    {
+	      status = geterrno_from_win_error (GetLastError (), EPERM);
+	      goto done;
+	    }
+	}
+    }
+  else
+    status = ESRCH;
+
+done:
+  return status;
+}
+
+int
+sched_setaffinity (pid_t pid, size_t sizeof_set, const cpu_set_t *set)
+{
+  int group = whichgroup (sizeof_set, set);
+  HANDLE process = 0;
+  int status = 0;
+
+  //XXX code assumes __get_cpus_per_group() is fixed at 64
+  pinfo p (pid ? pid : getpid ());
+  if (p)
+    {
+      process = pid && pid != myself->pid ?
+		OpenProcess (PROCESS_SET_INFORMATION, FALSE,
+			     p->dwProcessId) : GetCurrentProcess ();
+      if (wincap.has_processor_groups ())
+	{
+	  USHORT groupcount = CPU_GROUPMAX;
+	  USHORT grouparray[CPU_GROUPMAX];
+
+	  if (!GetProcessGroupAffinity (process, &groupcount, grouparray))
+	    {
+	      status = geterrno_from_win_error (GetLastError (), EPERM);
+	      goto done;
+	    }
+	  if (group < 0)
+	    {
+	      status = EINVAL;
+	      goto done;
+	    }
+	  if (groupcount == 1 && grouparray[0] == group)
+	    {
+	      if (!SetProcessAffinityMask (process, set[group]))
+		status = geterrno_from_win_error (GetLastError (), EPERM);
+	      goto done;
+	    }
+	  status = ENOSYS; //XXX can't do it without heroic measures
+	  goto done;
+	}
+      else
+	{
+	  if (group != 0)
+	    {
+	      status = EINVAL;
+	      goto done;
+	    }
+	  if (!SetProcessAffinityMask (process, set[0]))
+	    {
+	      status = geterrno_from_win_error (GetLastError (), EPERM);
+	      goto done;
+	    }
+	}
+    }
+  else
+    status = ESRCH;
+
+done:
+  if (process && process != GetCurrentProcess ())
+    CloseHandle (process);
+
+  return status;
+}
+
 } /* extern C */
diff --git a/winsup/cygwin/thread.cc b/winsup/cygwin/thread.cc
index f353dd497..43a6c88b3 100644
--- a/winsup/cygwin/thread.cc
+++ b/winsup/cygwin/thread.cc
@@ -23,6 +23,7 @@ details. */
 #include "winsup.h"
 #include "miscfuncs.h"
 #include "path.h"
+#include <sched.h>
 #include <stdlib.h>
 #include "sigproc.h"
 #include "fhandler.h"
@@ -2606,6 +2607,24 @@ pthread_timedjoin_np (pthread_t thread, void **return_val,
   return pthread::join (&thread, (void **) return_val, &timeout);
 }
 
+extern "C" int
+pthread_getaffinity_np (pthread_t thread, size_t sizeof_set, cpu_set_t *set)
+{
+  if (!pthread::is_good_object (&thread))
+    return ESRCH;
+
+  return sched_get_thread_affinity (thread->win32_obj_id, sizeof_set, set);
+}
+
+extern "C" int
+pthread_setaffinity_np (pthread_t thread, size_t sizeof_set, const cpu_set_t *set)
+{
+  if (!pthread::is_good_object (&thread))
+    return ESRCH;
+
+  return sched_set_thread_affinity (thread->win32_obj_id, sizeof_set, set);
+}
+
 extern "C" int
 pthread_getattr_np (pthread_t thread, pthread_attr_t *attr)
 {
-- 
2.17.0



More information about the Cygwin-patches mailing list