|
@@ -14,6 +14,8 @@
|
|
|
* 2003-10-22 Updates by Stephen Hemminger.
|
|
|
* 2004 May-July Rework by Paul Jackson.
|
|
|
* 2006 Rework by Paul Menage to use generic cgroups
|
|
|
+ * 2008 Rework of the scheduler domains and CPU hotplug handling
|
|
|
+ * by Max Krasnyansky
|
|
|
*
|
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
|
* License. See the file COPYING in the main directory of the Linux
|
|
@@ -236,9 +238,11 @@ static struct cpuset top_cpuset = {
|
|
|
|
|
|
static DEFINE_MUTEX(callback_mutex);
|
|
|
|
|
|
-/* This is ugly, but preserves the userspace API for existing cpuset
|
|
|
+/*
|
|
|
+ * This is ugly, but preserves the userspace API for existing cpuset
|
|
|
* users. If someone tries to mount the "cpuset" filesystem, we
|
|
|
- * silently switch it to mount "cgroup" instead */
|
|
|
+ * silently switch it to mount "cgroup" instead
|
|
|
+ */
|
|
|
static int cpuset_get_sb(struct file_system_type *fs_type,
|
|
|
int flags, const char *unused_dev_name,
|
|
|
void *data, struct vfsmount *mnt)
|
|
@@ -473,10 +477,9 @@ static int validate_change(const struct cpuset *cur, const struct cpuset *trial)
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * Helper routine for rebuild_sched_domains().
|
|
|
+ * Helper routine for generate_sched_domains().
|
|
|
* Do cpusets a, b have overlapping cpus_allowed masks?
|
|
|
*/
|
|
|
-
|
|
|
static int cpusets_overlap(struct cpuset *a, struct cpuset *b)
|
|
|
{
|
|
|
return cpus_intersects(a->cpus_allowed, b->cpus_allowed);
|
|
@@ -518,26 +521,15 @@ update_domain_attr_tree(struct sched_domain_attr *dattr, struct cpuset *c)
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * rebuild_sched_domains()
|
|
|
- *
|
|
|
- * This routine will be called to rebuild the scheduler's dynamic
|
|
|
- * sched domains:
|
|
|
- * - if the flag 'sched_load_balance' of any cpuset with non-empty
|
|
|
- * 'cpus' changes,
|
|
|
- * - or if the 'cpus' allowed changes in any cpuset which has that
|
|
|
- * flag enabled,
|
|
|
- * - or if the 'sched_relax_domain_level' of any cpuset which has
|
|
|
- * that flag enabled and with non-empty 'cpus' changes,
|
|
|
- * - or if any cpuset with non-empty 'cpus' is removed,
|
|
|
- * - or if a cpu gets offlined.
|
|
|
- *
|
|
|
- * This routine builds a partial partition of the systems CPUs
|
|
|
- * (the set of non-overlappping cpumask_t's in the array 'part'
|
|
|
- * below), and passes that partial partition to the kernel/sched.c
|
|
|
- * partition_sched_domains() routine, which will rebuild the
|
|
|
- * schedulers load balancing domains (sched domains) as specified
|
|
|
- * by that partial partition. A 'partial partition' is a set of
|
|
|
- * non-overlapping subsets whose union is a subset of that set.
|
|
|
+ * generate_sched_domains()
|
|
|
+ *
|
|
|
+ * This function builds a partial partition of the systems CPUs
|
|
|
+ * A 'partial partition' is a set of non-overlapping subsets whose
|
|
|
+ * union is a subset of that set.
|
|
|
+ * The output of this function needs to be passed to kernel/sched.c
|
|
|
+ * partition_sched_domains() routine, which will rebuild the scheduler's
|
|
|
+ * load balancing domains (sched domains) as specified by that partial
|
|
|
+ * partition.
|
|
|
*
|
|
|
* See "What is sched_load_balance" in Documentation/cpusets.txt
|
|
|
* for a background explanation of this.
|
|
@@ -547,13 +539,7 @@ update_domain_attr_tree(struct sched_domain_attr *dattr, struct cpuset *c)
|
|
|
* domains when operating in the severe memory shortage situations
|
|
|
* that could cause allocation failures below.
|
|
|
*
|
|
|
- * Call with cgroup_mutex held. May take callback_mutex during
|
|
|
- * call due to the kfifo_alloc() and kmalloc() calls. May nest
|
|
|
- * a call to the get_online_cpus()/put_online_cpus() pair.
|
|
|
- * Must not be called holding callback_mutex, because we must not
|
|
|
- * call get_online_cpus() while holding callback_mutex. Elsewhere
|
|
|
- * the kernel nests callback_mutex inside get_online_cpus() calls.
|
|
|
- * So the reverse nesting would risk an ABBA deadlock.
|
|
|
+ * Must be called with cgroup_lock held.
|
|
|
*
|
|
|
* The three key local variables below are:
|
|
|
* q - a linked-list queue of cpuset pointers, used to implement a
|
|
@@ -588,10 +574,10 @@ update_domain_attr_tree(struct sched_domain_attr *dattr, struct cpuset *c)
|
|
|
* element of the partition (one sched domain) to be passed to
|
|
|
* partition_sched_domains().
|
|
|
*/
|
|
|
-
|
|
|
-void rebuild_sched_domains(void)
|
|
|
+static int generate_sched_domains(cpumask_t **domains,
|
|
|
+ struct sched_domain_attr **attributes)
|
|
|
{
|
|
|
- LIST_HEAD(q); /* queue of cpusets to be scanned*/
|
|
|
+ LIST_HEAD(q); /* queue of cpusets to be scanned */
|
|
|
struct cpuset *cp; /* scans q */
|
|
|
struct cpuset **csa; /* array of all cpuset ptrs */
|
|
|
int csn; /* how many cpuset ptrs in csa so far */
|
|
@@ -601,23 +587,26 @@ void rebuild_sched_domains(void)
|
|
|
int ndoms; /* number of sched domains in result */
|
|
|
int nslot; /* next empty doms[] cpumask_t slot */
|
|
|
|
|
|
- csa = NULL;
|
|
|
+ ndoms = 0;
|
|
|
doms = NULL;
|
|
|
dattr = NULL;
|
|
|
+ csa = NULL;
|
|
|
|
|
|
/* Special case for the 99% of systems with one, full, sched domain */
|
|
|
if (is_sched_load_balance(&top_cpuset)) {
|
|
|
- ndoms = 1;
|
|
|
doms = kmalloc(sizeof(cpumask_t), GFP_KERNEL);
|
|
|
if (!doms)
|
|
|
- goto rebuild;
|
|
|
+ goto done;
|
|
|
+
|
|
|
dattr = kmalloc(sizeof(struct sched_domain_attr), GFP_KERNEL);
|
|
|
if (dattr) {
|
|
|
*dattr = SD_ATTR_INIT;
|
|
|
update_domain_attr_tree(dattr, &top_cpuset);
|
|
|
}
|
|
|
*doms = top_cpuset.cpus_allowed;
|
|
|
- goto rebuild;
|
|
|
+
|
|
|
+ ndoms = 1;
|
|
|
+ goto done;
|
|
|
}
|
|
|
|
|
|
csa = kmalloc(number_of_cpusets * sizeof(cp), GFP_KERNEL);
|
|
@@ -680,61 +669,141 @@ restart:
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /* Convert <csn, csa> to <ndoms, doms> */
|
|
|
+ /*
|
|
|
+ * Now we know how many domains to create.
|
|
|
+ * Convert <csn, csa> to <ndoms, doms> and populate cpu masks.
|
|
|
+ */
|
|
|
doms = kmalloc(ndoms * sizeof(cpumask_t), GFP_KERNEL);
|
|
|
- if (!doms)
|
|
|
- goto rebuild;
|
|
|
+ if (!doms) {
|
|
|
+ ndoms = 0;
|
|
|
+ goto done;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The rest of the code, including the scheduler, can deal with
|
|
|
+ * dattr==NULL case. No need to abort if alloc fails.
|
|
|
+ */
|
|
|
dattr = kmalloc(ndoms * sizeof(struct sched_domain_attr), GFP_KERNEL);
|
|
|
|
|
|
for (nslot = 0, i = 0; i < csn; i++) {
|
|
|
struct cpuset *a = csa[i];
|
|
|
+ cpumask_t *dp;
|
|
|
int apn = a->pn;
|
|
|
|
|
|
- if (apn >= 0) {
|
|
|
- cpumask_t *dp = doms + nslot;
|
|
|
-
|
|
|
- if (nslot == ndoms) {
|
|
|
- static int warnings = 10;
|
|
|
- if (warnings) {
|
|
|
- printk(KERN_WARNING
|
|
|
- "rebuild_sched_domains confused:"
|
|
|
- " nslot %d, ndoms %d, csn %d, i %d,"
|
|
|
- " apn %d\n",
|
|
|
- nslot, ndoms, csn, i, apn);
|
|
|
- warnings--;
|
|
|
- }
|
|
|
- continue;
|
|
|
+ if (apn < 0) {
|
|
|
+ /* Skip completed partitions */
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ dp = doms + nslot;
|
|
|
+
|
|
|
+ if (nslot == ndoms) {
|
|
|
+ static int warnings = 10;
|
|
|
+ if (warnings) {
|
|
|
+ printk(KERN_WARNING
|
|
|
+ "rebuild_sched_domains confused:"
|
|
|
+ " nslot %d, ndoms %d, csn %d, i %d,"
|
|
|
+ " apn %d\n",
|
|
|
+ nslot, ndoms, csn, i, apn);
|
|
|
+ warnings--;
|
|
|
}
|
|
|
+ continue;
|
|
|
+ }
|
|
|
|
|
|
- cpus_clear(*dp);
|
|
|
- if (dattr)
|
|
|
- *(dattr + nslot) = SD_ATTR_INIT;
|
|
|
- for (j = i; j < csn; j++) {
|
|
|
- struct cpuset *b = csa[j];
|
|
|
-
|
|
|
- if (apn == b->pn) {
|
|
|
- cpus_or(*dp, *dp, b->cpus_allowed);
|
|
|
- b->pn = -1;
|
|
|
- if (dattr)
|
|
|
- update_domain_attr_tree(dattr
|
|
|
- + nslot, b);
|
|
|
- }
|
|
|
+ cpus_clear(*dp);
|
|
|
+ if (dattr)
|
|
|
+ *(dattr + nslot) = SD_ATTR_INIT;
|
|
|
+ for (j = i; j < csn; j++) {
|
|
|
+ struct cpuset *b = csa[j];
|
|
|
+
|
|
|
+ if (apn == b->pn) {
|
|
|
+ cpus_or(*dp, *dp, b->cpus_allowed);
|
|
|
+ if (dattr)
|
|
|
+ update_domain_attr_tree(dattr + nslot, b);
|
|
|
+
|
|
|
+ /* Done with this partition */
|
|
|
+ b->pn = -1;
|
|
|
}
|
|
|
- nslot++;
|
|
|
}
|
|
|
+ nslot++;
|
|
|
}
|
|
|
BUG_ON(nslot != ndoms);
|
|
|
|
|
|
-rebuild:
|
|
|
- /* Have scheduler rebuild sched domains */
|
|
|
+done:
|
|
|
+ kfree(csa);
|
|
|
+
|
|
|
+ *domains = doms;
|
|
|
+ *attributes = dattr;
|
|
|
+ return ndoms;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Rebuild scheduler domains.
|
|
|
+ *
|
|
|
+ * Call with neither cgroup_mutex held nor within get_online_cpus().
|
|
|
+ * Takes both cgroup_mutex and get_online_cpus().
|
|
|
+ *
|
|
|
+ * Cannot be directly called from cpuset code handling changes
|
|
|
+ * to the cpuset pseudo-filesystem, because it cannot be called
|
|
|
+ * from code that already holds cgroup_mutex.
|
|
|
+ */
|
|
|
+static void do_rebuild_sched_domains(struct work_struct *unused)
|
|
|
+{
|
|
|
+ struct sched_domain_attr *attr;
|
|
|
+ cpumask_t *doms;
|
|
|
+ int ndoms;
|
|
|
+
|
|
|
get_online_cpus();
|
|
|
- partition_sched_domains(ndoms, doms, dattr);
|
|
|
+
|
|
|
+ /* Generate domain masks and attrs */
|
|
|
+ cgroup_lock();
|
|
|
+ ndoms = generate_sched_domains(&doms, &attr);
|
|
|
+ cgroup_unlock();
|
|
|
+
|
|
|
+ /* Have scheduler rebuild the domains */
|
|
|
+ partition_sched_domains(ndoms, doms, attr);
|
|
|
+
|
|
|
put_online_cpus();
|
|
|
+}
|
|
|
|
|
|
-done:
|
|
|
- kfree(csa);
|
|
|
- /* Don't kfree(doms) -- partition_sched_domains() does that. */
|
|
|
- /* Don't kfree(dattr) -- partition_sched_domains() does that. */
|
|
|
+static DECLARE_WORK(rebuild_sched_domains_work, do_rebuild_sched_domains);
|
|
|
+
|
|
|
+/*
|
|
|
+ * Rebuild scheduler domains, asynchronously via workqueue.
|
|
|
+ *
|
|
|
+ * If the flag 'sched_load_balance' of any cpuset with non-empty
|
|
|
+ * 'cpus' changes, or if the 'cpus' allowed changes in any cpuset
|
|
|
+ * which has that flag enabled, or if any cpuset with a non-empty
|
|
|
+ * 'cpus' is removed, then call this routine to rebuild the
|
|
|
+ * scheduler's dynamic sched domains.
|
|
|
+ *
|
|
|
+ * The rebuild_sched_domains() and partition_sched_domains()
|
|
|
+ * routines must nest cgroup_lock() inside get_online_cpus(),
|
|
|
+ * but such cpuset changes as these must nest that locking the
|
|
|
+ * other way, holding cgroup_lock() for much of the code.
|
|
|
+ *
|
|
|
+ * So in order to avoid an ABBA deadlock, the cpuset code handling
|
|
|
+ * these user changes delegates the actual sched domain rebuilding
|
|
|
+ * to a separate workqueue thread, which ends up processing the
|
|
|
+ * above do_rebuild_sched_domains() function.
|
|
|
+ */
|
|
|
+static void async_rebuild_sched_domains(void)
|
|
|
+{
|
|
|
+ schedule_work(&rebuild_sched_domains_work);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Accomplishes the same scheduler domain rebuild as the above
|
|
|
+ * async_rebuild_sched_domains(), however it directly calls the
|
|
|
+ * rebuild routine synchronously rather than calling it via an
|
|
|
+ * asynchronous work thread.
|
|
|
+ *
|
|
|
+ * This can only be called from code that is not holding
|
|
|
+ * cgroup_mutex (not nested in a cgroup_lock() call.)
|
|
|
+ */
|
|
|
+void rebuild_sched_domains(void)
|
|
|
+{
|
|
|
+ do_rebuild_sched_domains(NULL);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -863,7 +932,7 @@ static int update_cpumask(struct cpuset *cs, const char *buf)
|
|
|
return retval;
|
|
|
|
|
|
if (is_load_balanced)
|
|
|
- rebuild_sched_domains();
|
|
|
+ async_rebuild_sched_domains();
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -1090,7 +1159,7 @@ static int update_relax_domain_level(struct cpuset *cs, s64 val)
|
|
|
if (val != cs->relax_domain_level) {
|
|
|
cs->relax_domain_level = val;
|
|
|
if (!cpus_empty(cs->cpus_allowed) && is_sched_load_balance(cs))
|
|
|
- rebuild_sched_domains();
|
|
|
+ async_rebuild_sched_domains();
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
@@ -1131,7 +1200,7 @@ static int update_flag(cpuset_flagbits_t bit, struct cpuset *cs,
|
|
|
mutex_unlock(&callback_mutex);
|
|
|
|
|
|
if (cpus_nonempty && balance_flag_changed)
|
|
|
- rebuild_sched_domains();
|
|
|
+ async_rebuild_sched_domains();
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -1492,6 +1561,9 @@ static u64 cpuset_read_u64(struct cgroup *cont, struct cftype *cft)
|
|
|
default:
|
|
|
BUG();
|
|
|
}
|
|
|
+
|
|
|
+ /* Unreachable but makes gcc happy */
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
static s64 cpuset_read_s64(struct cgroup *cont, struct cftype *cft)
|
|
@@ -1504,6 +1576,9 @@ static s64 cpuset_read_s64(struct cgroup *cont, struct cftype *cft)
|
|
|
default:
|
|
|
BUG();
|
|
|
}
|
|
|
+
|
|
|
+ /* Unrechable but makes gcc happy */
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -1692,15 +1767,9 @@ static struct cgroup_subsys_state *cpuset_create(
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * Locking note on the strange update_flag() call below:
|
|
|
- *
|
|
|
* If the cpuset being removed has its flag 'sched_load_balance'
|
|
|
* enabled, then simulate turning sched_load_balance off, which
|
|
|
- * will call rebuild_sched_domains(). The get_online_cpus()
|
|
|
- * call in rebuild_sched_domains() must not be made while holding
|
|
|
- * callback_mutex. Elsewhere the kernel nests callback_mutex inside
|
|
|
- * get_online_cpus() calls. So the reverse nesting would risk an
|
|
|
- * ABBA deadlock.
|
|
|
+ * will call async_rebuild_sched_domains().
|
|
|
*/
|
|
|
|
|
|
static void cpuset_destroy(struct cgroup_subsys *ss, struct cgroup *cont)
|
|
@@ -1719,7 +1788,7 @@ static void cpuset_destroy(struct cgroup_subsys *ss, struct cgroup *cont)
|
|
|
struct cgroup_subsys cpuset_subsys = {
|
|
|
.name = "cpuset",
|
|
|
.create = cpuset_create,
|
|
|
- .destroy = cpuset_destroy,
|
|
|
+ .destroy = cpuset_destroy,
|
|
|
.can_attach = cpuset_can_attach,
|
|
|
.attach = cpuset_attach,
|
|
|
.populate = cpuset_populate,
|
|
@@ -1811,7 +1880,7 @@ static void move_member_tasks_to_cpuset(struct cpuset *from, struct cpuset *to)
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * If common_cpu_mem_hotplug_unplug(), below, unplugs any CPUs
|
|
|
+ * If CPU and/or memory hotplug handlers, below, unplug any CPUs
|
|
|
* or memory nodes, we need to walk over the cpuset hierarchy,
|
|
|
* removing that CPU or node from all cpusets. If this removes the
|
|
|
* last CPU or node from a cpuset, then move the tasks in the empty
|
|
@@ -1902,35 +1971,6 @@ static void scan_for_empty_cpusets(const struct cpuset *root)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * The cpus_allowed and mems_allowed nodemasks in the top_cpuset track
|
|
|
- * cpu_online_map and node_states[N_HIGH_MEMORY]. Force the top cpuset to
|
|
|
- * track what's online after any CPU or memory node hotplug or unplug event.
|
|
|
- *
|
|
|
- * Since there are two callers of this routine, one for CPU hotplug
|
|
|
- * events and one for memory node hotplug events, we could have coded
|
|
|
- * two separate routines here. We code it as a single common routine
|
|
|
- * in order to minimize text size.
|
|
|
- */
|
|
|
-
|
|
|
-static void common_cpu_mem_hotplug_unplug(int rebuild_sd)
|
|
|
-{
|
|
|
- cgroup_lock();
|
|
|
-
|
|
|
- top_cpuset.cpus_allowed = cpu_online_map;
|
|
|
- top_cpuset.mems_allowed = node_states[N_HIGH_MEMORY];
|
|
|
- scan_for_empty_cpusets(&top_cpuset);
|
|
|
-
|
|
|
- /*
|
|
|
- * Scheduler destroys domains on hotplug events.
|
|
|
- * Rebuild them based on the current settings.
|
|
|
- */
|
|
|
- if (rebuild_sd)
|
|
|
- rebuild_sched_domains();
|
|
|
-
|
|
|
- cgroup_unlock();
|
|
|
-}
|
|
|
-
|
|
|
/*
|
|
|
* The top_cpuset tracks what CPUs and Memory Nodes are online,
|
|
|
* period. This is necessary in order to make cpusets transparent
|
|
@@ -1939,40 +1979,52 @@ static void common_cpu_mem_hotplug_unplug(int rebuild_sd)
|
|
|
*
|
|
|
* This routine ensures that top_cpuset.cpus_allowed tracks
|
|
|
* cpu_online_map on each CPU hotplug (cpuhp) event.
|
|
|
+ *
|
|
|
+ * Called within get_online_cpus(). Needs to call cgroup_lock()
|
|
|
+ * before calling generate_sched_domains().
|
|
|
*/
|
|
|
-
|
|
|
-static int cpuset_handle_cpuhp(struct notifier_block *unused_nb,
|
|
|
+static int cpuset_track_online_cpus(struct notifier_block *unused_nb,
|
|
|
unsigned long phase, void *unused_cpu)
|
|
|
{
|
|
|
+ struct sched_domain_attr *attr;
|
|
|
+ cpumask_t *doms;
|
|
|
+ int ndoms;
|
|
|
+
|
|
|
switch (phase) {
|
|
|
- case CPU_UP_CANCELED:
|
|
|
- case CPU_UP_CANCELED_FROZEN:
|
|
|
- case CPU_DOWN_FAILED:
|
|
|
- case CPU_DOWN_FAILED_FROZEN:
|
|
|
case CPU_ONLINE:
|
|
|
case CPU_ONLINE_FROZEN:
|
|
|
case CPU_DEAD:
|
|
|
case CPU_DEAD_FROZEN:
|
|
|
- common_cpu_mem_hotplug_unplug(1);
|
|
|
break;
|
|
|
+
|
|
|
default:
|
|
|
return NOTIFY_DONE;
|
|
|
}
|
|
|
|
|
|
+ cgroup_lock();
|
|
|
+ top_cpuset.cpus_allowed = cpu_online_map;
|
|
|
+ scan_for_empty_cpusets(&top_cpuset);
|
|
|
+ ndoms = generate_sched_domains(&doms, &attr);
|
|
|
+ cgroup_unlock();
|
|
|
+
|
|
|
+ /* Have scheduler rebuild the domains */
|
|
|
+ partition_sched_domains(ndoms, doms, attr);
|
|
|
+
|
|
|
return NOTIFY_OK;
|
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_MEMORY_HOTPLUG
|
|
|
/*
|
|
|
* Keep top_cpuset.mems_allowed tracking node_states[N_HIGH_MEMORY].
|
|
|
- * Call this routine anytime after you change
|
|
|
- * node_states[N_HIGH_MEMORY].
|
|
|
- * See also the previous routine cpuset_handle_cpuhp().
|
|
|
+ * Call this routine anytime after node_states[N_HIGH_MEMORY] changes.
|
|
|
+ * See also the previous routine cpuset_track_online_cpus().
|
|
|
*/
|
|
|
-
|
|
|
void cpuset_track_online_nodes(void)
|
|
|
{
|
|
|
- common_cpu_mem_hotplug_unplug(0);
|
|
|
+ cgroup_lock();
|
|
|
+ top_cpuset.mems_allowed = node_states[N_HIGH_MEMORY];
|
|
|
+ scan_for_empty_cpusets(&top_cpuset);
|
|
|
+ cgroup_unlock();
|
|
|
}
|
|
|
#endif
|
|
|
|
|
@@ -1987,7 +2039,7 @@ void __init cpuset_init_smp(void)
|
|
|
top_cpuset.cpus_allowed = cpu_online_map;
|
|
|
top_cpuset.mems_allowed = node_states[N_HIGH_MEMORY];
|
|
|
|
|
|
- hotcpu_notifier(cpuset_handle_cpuhp, 0);
|
|
|
+ hotcpu_notifier(cpuset_track_online_cpus, 0);
|
|
|
}
|
|
|
|
|
|
/**
|