kernel/sched: CPU mask affinity/pinning API
This adds a simple implementation of SMP CPU affinity to Zephyr. The API is simple and doesn't try to invent abstractions like "cpu sets". Each thread has an enable/disable flag associated with each CPU in the system, and the bits can be turned on and off (for threads that are not currently runnable, of course) using an easy three-function API. Because the implementation picked requires enumerating runnable threads in priority order looking for one that match the current CPU, this is not a good fit for the SCALABLE or MULTIQ scheduler backends, so it currently can be enabled only for SCHED_DUMB (which is the default anyway). Fancier algorithms do exist, but even the best of them scale as O(N_CPUS), so aren't quite constant time and often require significant memory overhead to keep separate lists for different cpus/sets. The intended use here is for apps that want to "pin" threads to specific CPUs for latency control, or conversely to prevent certain threads from taking time on specific CPUs to leave them free for fast response. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
parent
54d27263fc
commit
ab46b1b3c5
5 changed files with 145 additions and 2 deletions
|
@ -452,6 +452,12 @@ struct _thread_base {
|
|||
|
||||
/* Recursive count of irq_lock() calls */
|
||||
u8_t global_lock_count;
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SCHED_CPU_MASK
|
||||
/* "May run on" bits for each CPU */
|
||||
u8_t cpu_mask;
|
||||
#endif
|
||||
|
||||
/* data returned by APIs */
|
||||
|
@ -1035,6 +1041,52 @@ __syscall void k_thread_priority_set(k_tid_t thread, int prio);
|
|||
__syscall void k_thread_deadline_set(k_tid_t thread, int deadline);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SCHED_CPU_MASK
|
||||
/**
|
||||
* @brief Sets all CPU enable masks to zero
|
||||
*
|
||||
* After this returns, the thread will no longer be schedulable on any
|
||||
* CPUs. The thread must not be currently runnable.
|
||||
*
|
||||
* @param thread Thread to operate upon
|
||||
* @return Zero on success, otherwise error code
|
||||
*/
|
||||
int k_thread_cpu_mask_clear(k_tid_t thread);
|
||||
|
||||
/**
|
||||
* @brief Sets all CPU enable masks to one
|
||||
*
|
||||
* After this returns, the thread will be schedulable on any CPU. The
|
||||
* thread must not be currently runnable.
|
||||
*
|
||||
* @param thread Thread to operate upon
|
||||
* @return Zero on success, otherwise error code
|
||||
*/
|
||||
int k_thread_cpu_mask_enable_all(k_tid_t thread);
|
||||
|
||||
/**
|
||||
* @brief Enable thread to run on specified CPU
|
||||
*
|
||||
* The thread must not be currently runnable.
|
||||
*
|
||||
* @param thread Thread to operate upon
|
||||
* @param cpu CPU index
|
||||
* @return Zero on success, otherwise error code
|
||||
*/
|
||||
int k_thread_cpu_mask_enable(k_tid_t thread, int cpu);
|
||||
|
||||
/**
|
||||
* @brief Prevent thread to run on specified CPU
|
||||
*
|
||||
* The thread must not be currently runnable.
|
||||
*
|
||||
* @param thread Thread to operate upon
|
||||
* @param cpu CPU index
|
||||
* @return Zero on success, otherwise error code
|
||||
*/
|
||||
int k_thread_cpu_mask_disable(k_tid_t thread, int cpu);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Suspend a thread.
|
||||
*
|
||||
|
|
|
@ -125,6 +125,24 @@ config SCHED_DEADLINE
|
|||
single priority will choose the next expiring deadline and
|
||||
not simply the least recently added thread.
|
||||
|
||||
config SCHED_CPU_MASK
|
||||
bool "Enable CPU mask affinity/pinning API"
|
||||
depends on SCHED_DUMB
|
||||
help
|
||||
When true, the app will have access to the
|
||||
z_thread_*_cpu_mask() APIs which control per-CPU affinity
|
||||
masks in SMP mode, allowing apps to pin threads to specific
|
||||
CPUs or disallow threads from running on given CPUs. Note
|
||||
that as currently implemented, this involves an inherent
|
||||
O(N) scaling in the number of idle-but-runnable threads, and
|
||||
thus works only with the DUMB scheduler (as SCALABLE and
|
||||
MULTIQ would see no benefit).
|
||||
|
||||
Note that this setting does not technically depend on SMP
|
||||
and is implemented without it for testing purposes, but for
|
||||
obvious reasons makes sense as an application API only where
|
||||
there is more than one CPU. With one CPU, it's just a
|
||||
higher overhead version of k_thread_start/stop().
|
||||
|
||||
config MAIN_STACK_SIZE
|
||||
int "Size of stack for initialization and main thread"
|
||||
|
|
|
@ -471,7 +471,12 @@ FUNC_NORETURN void _Cstart(void)
|
|||
kernel_arch_init();
|
||||
|
||||
#ifdef CONFIG_MULTITHREADING
|
||||
struct k_thread dummy_thread = { .base.thread_state = _THREAD_DUMMY };
|
||||
struct k_thread dummy_thread = {
|
||||
.base.thread_state = _THREAD_DUMMY,
|
||||
# ifdef CONFIG_SCHED_CPU_MASK
|
||||
.base.cpu_mask = -1,
|
||||
# endif
|
||||
};
|
||||
|
||||
_current = &dummy_thread;
|
||||
#endif
|
||||
|
|
|
@ -17,7 +17,11 @@
|
|||
#if defined(CONFIG_SCHED_DUMB)
|
||||
#define _priq_run_add _priq_dumb_add
|
||||
#define _priq_run_remove _priq_dumb_remove
|
||||
#define _priq_run_best _priq_dumb_best
|
||||
# if defined(CONFIG_SCHED_CPU_MASK)
|
||||
# define _priq_run_best _priq_dumb_mask_best
|
||||
# else
|
||||
# define _priq_run_best _priq_dumb_best
|
||||
# endif
|
||||
#elif defined(CONFIG_SCHED_SCALABLE)
|
||||
#define _priq_run_add _priq_rb_add
|
||||
#define _priq_run_remove _priq_rb_remove
|
||||
|
@ -155,6 +159,23 @@ static ALWAYS_INLINE bool should_preempt(struct k_thread *th, int preempt_ok)
|
|||
return false;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SCHED_CPU_MASK
|
||||
static ALWAYS_INLINE struct k_thread *_priq_dumb_mask_best(sys_dlist_t *pq)
|
||||
{
|
||||
/* With masks enabled we need to be prepared to walk the list
|
||||
* looking for one we can run
|
||||
*/
|
||||
struct k_thread *t;
|
||||
|
||||
SYS_DLIST_FOR_EACH_CONTAINER(pq, t, base.qnode_dlist) {
|
||||
if ((t->base.cpu_mask & BIT(_current_cpu->id)) != 0) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static ALWAYS_INLINE struct k_thread *next_up(void)
|
||||
{
|
||||
#ifndef CONFIG_SMP
|
||||
|
@ -933,3 +954,47 @@ int _impl_k_is_preempt_thread(void)
|
|||
#ifdef CONFIG_USERSPACE
|
||||
Z_SYSCALL_HANDLER0_SIMPLE(k_is_preempt_thread);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SCHED_CPU_MASK
|
||||
# ifdef CONFIG_SMP
|
||||
/* Right now we use a single byte for this mask */
|
||||
BUILD_ASSERT_MSG(CONFIG_MP_NUM_CPU <= 8, "Too many CPUs for mask word");
|
||||
# endif
|
||||
|
||||
|
||||
static int cpu_mask_mod(k_tid_t t, u32_t enable_mask, u32_t disable_mask)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
LOCKED(&sched_lock) {
|
||||
if (_is_thread_prevented_from_running(t)) {
|
||||
t->base.cpu_mask |= enable_mask;
|
||||
t->base.cpu_mask &= ~disable_mask;
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int k_thread_cpu_mask_clear(k_tid_t thread)
|
||||
{
|
||||
return cpu_mask_mod(thread, 0, 0xffffffff);
|
||||
}
|
||||
|
||||
int k_thread_cpu_mask_enable_all(k_tid_t thread)
|
||||
{
|
||||
return cpu_mask_mod(thread, 0xffffffff, 0);
|
||||
}
|
||||
|
||||
int k_thread_cpu_mask_enable(k_tid_t thread, int cpu)
|
||||
{
|
||||
return cpu_mask_mod(thread, BIT(cpu), 0);
|
||||
}
|
||||
|
||||
int k_thread_cpu_mask_disable(k_tid_t thread, int cpu)
|
||||
{
|
||||
return cpu_mask_mod(thread, 0, BIT(cpu));
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SCHED_CPU_MASK */
|
||||
|
|
|
@ -390,6 +390,9 @@ void _setup_new_thread(struct k_thread *new_thread,
|
|||
/* Any given thread has access to itself */
|
||||
k_object_access_grant(new_thread, new_thread);
|
||||
#endif
|
||||
#ifdef CONFIG_SCHED_CPU_MASK
|
||||
new_thread->base.cpu_mask = -1;
|
||||
#endif
|
||||
#ifdef CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN
|
||||
/* _current may be null if the dummy thread is not used */
|
||||
if (!_current) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue