Task groups can in a many-core system run on any CPU core, but to facilitate this, creating only one scheduling entity will not suffice. Groups thus must create a scheduling entity for every CPU core on the system. Scheduling entities across CPUs are represented by struct task_group:
/* task group related information */
struct task_group {
struct cgroup_subsys_state css;
#ifdef CONFIG_FAIR_GROUP_SCHED
/* schedulable entities of this group on each cpu */
struct sched_entity **se;
/* runqueue "owned" by this group on each cpu */
struct cfs_rq **cfs_rq;
unsigned long shares;
#ifdef CONFIG_SMP
/*
* load_avg can be heavily contended at clock tick time, so put
* it in its own cacheline separated from the fields above which
* will also be accessed at each tick.
*/
atomic_long_t load_avg ____cacheline_aligned;
#endif
#endif
#ifdef CONFIG_RT_GROUP_SCHED
struct sched_rt_entity **rt_se;
struct rt_rq **rt_rq;
struct rt_bandwidth rt_bandwidth;
#endif
struct rcu_head rcu;
struct list_head list;
struct task_group *parent;
struct list_head siblings;
struct list_head children;
#ifdef CONFIG_SCHED_AUTOGROUP
struct autogroup *autogroup;
#endif
struct cfs_bandwidth cfs_bandwidth;
};
Now every task group has a scheduling entity for every CPU core along with a CFS runqueue associated with it. When a task from one task group migrates from one CPU core (x) to another CPU core (y), the task is dequeued from the CFS runqueue of CPU x and enqueued to the CFS runqueue of CPU y.