Cygwin: Implement sched_[gs]etaffinity()

This patch set implements the Linux syscalls sched_getaffinity,
sched_setaffinity, pthread_getaffinity_np, and pthread_setaffinity_np.
Linux has a straightforward view of the cpu sets used in affinity masks.
They are simply long (1024-bit) bit masks.  This code emulates that view
while internally dealing with Windows' distribution of available CPUs among
processor groups.
This commit is contained in:
Mark Geisert 2019-06-23 14:51:06 -07:00 committed by Corinna Vinschen
parent d54edfdf81
commit 641ecb0753
11 changed files with 389 additions and 5 deletions

View File

@ -92,6 +92,29 @@ int sched_yield( void );
#if __GNU_VISIBLE
int sched_getcpu(void);
/* Affinity-related definitions, here until numerous enough to separate out */
#ifdef __x86_64__
typedef uint64_t __cpu_mask;
#else
typedef uint32_t __cpu_mask;
#endif
#define __CPU_SETSIZE 1024 // maximum number of logical processors tracked
#define __NCPUBITS (8 * sizeof (__cpu_mask)) // max size of processor group
#define __CPU_GROUPMAX (__CPU_SETSIZE / __NCPUBITS) // maximum group number
#define __CPUELT(cpu) ((cpu) / __NCPUBITS)
#define __CPUMASK(cpu) ((__cpu_mask) 1 << ((cpu) % __NCPUBITS))
typedef struct
{
__cpu_mask __bits[__CPU_GROUPMAX];
} cpu_set_t;
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
#ifdef __cplusplus

View File

@ -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

View File

@ -509,12 +509,14 @@ details. */
336: New Cygwin PID algorithm (yeah, not really an API change)
337: MOUNT_BINARY -> MOUNT_TEXT
338: Export secure_getenv.
339: Export sched_getaffinity, sched_setaffinity, pthread_getaffinity_np,
pthread_setaffinity_np.
Note that we forgot to bump the api for ualarm, strtoll, strtoull,
sigaltstack, sethostname. */
#define CYGWIN_VERSION_API_MAJOR 0
#define CYGWIN_VERSION_API_MINOR 338
#define CYGWIN_VERSION_API_MINOR 339
/* There is also a compatibity version number associated with the shared memory
regions. It is incremented when incompatible changes are made to the shared

View File

@ -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 *);

View File

@ -963,17 +963,19 @@ SetThreadName(DWORD dwThreadID, const char* threadName)
#define add_size(p,s) ((p) = ((__typeof__(p))((PBYTE)(p)+(s))))
static WORD num_cpu_per_group = 0;
static WORD group_count = 0;
WORD
__get_cpus_per_group (void)
{
static WORD num_cpu_per_group = 0;
tmp_pathbuf tp;
if (num_cpu_per_group)
return num_cpu_per_group;
num_cpu_per_group = 64;
group_count = 1;
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX lpi =
(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX) tp.c_get ();
@ -1005,10 +1007,20 @@ __get_cpus_per_group (void)
actually available CPUs. The ActiveProcessorCount is correct
though. So we just use ActiveProcessorCount for now, hoping for
the best. */
num_cpu_per_group
= plpi->Group.GroupInfo[0].ActiveProcessorCount;
num_cpu_per_group = plpi->Group.GroupInfo[0].ActiveProcessorCount;
/* Follow that lead to get the group count. */
group_count = plpi->Group.ActiveGroupCount;
break;
}
return num_cpu_per_group;
}
WORD
__get_group_count (void)
{
if (group_count == 0)
(void) __get_cpus_per_group (); // caller should have called this first
return group_count;
}

View File

@ -120,5 +120,6 @@ extern "C" HANDLE WINAPI CygwinCreateThread (LPTHREAD_START_ROUTINE thread_func,
void SetThreadName (DWORD dwThreadID, const char* threadName);
WORD __get_cpus_per_group (void);
WORD __get_group_count (void);
#endif /*_MISCFUNCS_H*/

View File

@ -5,6 +5,9 @@ What's new:
1703 or later. Add fake 24 bit color support for legacy console,
which uses the nearest color from 16 system colors.
- New APIs: sched_getaffinity, sched_setaffinity, pthread_getaffinity_np,
pthread_setaffinity_np.
What changed:
-------------

View File

@ -424,4 +424,312 @@ sched_getcpu ()
return pnum.Group * __get_cpus_per_group () + pnum.Number;
}
/* construct an affinity mask with just the 'count' lower-order bits set */
static __cpu_mask
groupmask (int count)
{
if (count >= (int) (NBBY * sizeof (__cpu_mask)))
return ~(__cpu_mask) 0;
else
return ((__cpu_mask) 1 << count) - 1;
}
/* return the affinity mask of the indicated group from the given cpu set */
static __cpu_mask
getgroup (size_t sizeof_set, const cpu_set_t *set, int groupnum)
{
int groupsize = __get_cpus_per_group ();
int bitindex = groupnum * groupsize;
int setsize = NBBY * sizeof_set; // bit size of whole cpu set
if (bitindex + groupsize > setsize)
return (__cpu_mask) 0;
int wordsize = NBBY * sizeof (cpu_set_t);
int wordindex = bitindex / wordsize;
__cpu_mask result = set->__bits[wordindex];
int offset = bitindex % wordsize;
if (offset)
{
result >>= offset;
offset = wordsize - offset;
}
else
offset = wordsize;
if (offset < groupsize)
result |= (set->__bits[wordindex + 1] << offset);
if (groupsize < wordsize)
result &= groupmask (groupsize);
return result;
}
/* set the given affinity mask for indicated group within the given cpu set */
static __cpu_mask
setgroup (size_t sizeof_set, cpu_set_t *set, int groupnum, __cpu_mask aff)
{
int groupsize = __get_cpus_per_group ();
int bitindex = groupnum * groupsize;
int setsize = NBBY * sizeof_set; // bit size of whole cpu set
if (bitindex + groupsize > setsize)
return (__cpu_mask) 0;
int wordsize = NBBY * sizeof (cpu_set_t);
int wordindex = bitindex / wordsize;
int offset = bitindex % wordsize;
__cpu_mask mask = groupmask (groupsize);
aff &= mask;
set->__bits[wordindex] &= ~(mask << offset);
set->__bits[wordindex] |= aff << offset;
if ((bitindex + groupsize - 1) / wordsize != wordindex)
{
offset = wordsize - offset;
set->__bits[wordindex + 1] &= ~(mask >> offset);
set->__bits[wordindex + 1] |= aff >> offset;
}
return aff;
}
/* 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)
{
int res = -1;
int maxgroup = min (__get_group_count (),
(NBBY * sizeof_set) / __get_cpus_per_group ());
for (int i = 0; i < maxgroup; ++i)
if (getgroup (sizeof_set, set, i))
{
if (res >= 0)
return -1; // error return if more than one group indicated
else
res = 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;
if (thread)
{
memset (set, 0, sizeof_set);
if (wincap.has_processor_groups () && __get_group_count () > 1)
{
GROUP_AFFINITY ga;
if (!GetThreadGroupAffinity (thread, &ga))
{
status = geterrno_from_win_error (GetLastError (), EPERM);
goto done;
}
setgroup (sizeof_set, set, ga.Group, ga.Mask);
}
else
{
THREAD_BASIC_INFORMATION tbi;
status = NtQueryInformationThread (thread, ThreadBasicInformation,
&tbi, sizeof (tbi), NULL);
if (NT_SUCCESS (status))
setgroup (sizeof_set, set, 0, tbi.AffinityMask);
else
status = geterrno_from_nt_status (status);
}
}
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;
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))
{
status = geterrno_from_win_error (GetLastError (), EPERM);
goto done;
}
memset (set, 0, sizeof_set);
if (wincap.has_processor_groups () && __get_group_count () > 1)
{
USHORT groupcount = __CPU_GROUPMAX;
USHORT grouparray[__CPU_GROUPMAX];
if (!GetProcessGroupAffinity (process, &groupcount, grouparray))
{
status = geterrno_from_win_error (GetLastError (), EPERM);
goto done;
}
KAFFINITY miscmask = groupmask (__get_cpus_per_group ());
for (int i = 0; i < groupcount; i++)
setgroup (sizeof_set, set, grouparray[i], miscmask);
}
else
setgroup (sizeof_set, set, 0, procmask);
}
else
status = ESRCH;
done:
if (process && process != GetCurrentProcess ())
CloseHandle (process);
if (status)
{
set_errno (status);
status = -1;
}
else
{
/* Emulate documented Linux kernel behavior on successful return */
status = wincap.cpu_count ();
}
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;
if (thread)
{
if (wincap.has_processor_groups () && __get_group_count () > 1)
{
GROUP_AFFINITY ga;
if (group < 0)
{
status = EINVAL;
goto done;
}
memset (&ga, 0, sizeof (ga));
ga.Mask = getgroup (sizeof_set, 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, getgroup (sizeof_set, 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;
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 () && __get_group_count () > 1)
{
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, getgroup (sizeof_set, set, group)))
status = geterrno_from_win_error (GetLastError (), EPERM);
goto done;
}
/* If we get here, the user is trying to add the process to another
group or move it from current group to another group. These ops
are not allowed by Windows. One has to move one or more of the
process' threads to the new group(s) one by one. Here, we bail.
*/
status = EINVAL;
goto done;
}
else
{
if (group != 0)
{
status = EINVAL;
goto done;
}
if (!SetProcessAffinityMask (process, getgroup (sizeof_set, set, 0)))
{
status = geterrno_from_win_error (GetLastError (), EPERM);
goto done;
}
}
}
else
status = ESRCH;
done:
if (process && process != GetCurrentProcess ())
CloseHandle (process);
if (status)
{
set_errno (status);
status = -1;
}
return status;
}
} /* extern C */

View File

@ -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)
{

View File

@ -29,6 +29,12 @@ If a SA_SIGINFO signal handler changes the ucontext_t pointed to by the
third parameter, follow it after returning from the handler.
</para></listitem>
<listitem><para>
Support for getting and setting process and thread affinities. New APIs:
sched_getaffinity, sched_setaffinity, pthread_getaffinity_np,
pthread_setaffinity_np.
</para></listitem>
</itemizedlist>
</sect2>

View File

@ -1359,8 +1359,10 @@ also IEEE Std 1003.1-2008 (POSIX.1-2008).</para>
pow10f
pow10l
ppoll
pthread_getaffinity_np
pthread_getattr_np
pthread_getname_np
pthread_setaffinity_np
pthread_setname_np
pthread_sigqueue
pthread_timedjoin_np
@ -1374,7 +1376,9 @@ also IEEE Std 1003.1-2008 (POSIX.1-2008).</para>
rawmemchr
removexattr
scandirat
sched_getaffinity
sched_getcpu
sched_setaffinity
secure_getenv
setxattr
signalfd