History

sched/uclamp: Extend sched_setattr() to support utilization clamping

The SCHED_DEADLINE scheduling class provides an advanced and formal model to define tasks requirements that can translate into proper decisions for both task placements and frequencies selections. Other classes have a more simplified model based on the POSIX concept of priorities. Such a simple priority based model however does not allow to exploit most advanced features of the Linux scheduler like, for example, driving frequencies selection via the schedutil cpufreq governor. However, also for non SCHED_DEADLINE tasks, it's still interesting to define tasks properties to support scheduler decisions. Utilization clamping exposes to user-space a new set of per-task attributes the scheduler can use as hints about the expected/required utilization for a task. This allows to implement a "proactive" per-task frequency control policy, a more advanced policy than the current one based just on "passive" measured task utilization. For example, it's possible to boost interactive tasks (e.g. to get better performance) or cap background tasks (e.g. to be more energy/thermal efficient). Introduce a new API to set utilization clamping values for a specified task by extending sched_setattr(), a syscall which already allows to define task specific properties for different scheduling classes. A new pair of attributes allows to specify a minimum and maximum utilization the scheduler can consider for a task. Do that by validating the required clamp values before and then applying the required changes using _the_ same pattern already in use for __setscheduler(). This ensures that the task is re-enqueued with the new clamp values.

Patrick Bellasi committed 7 years ago

Commit a509a7c1 parent 1d6362f4 files · +161 −17Browse files

Unified · Split

4 files
  1. include/linux/sched.h +9 −0
  2. include/uapi/linux/sched.h +11 −1
  3. include/uapi/linux/sched/types.h +57 −9
  4. kernel/sched/core.c +84 −7
include/linux/sched.h+9−0
@@ -587,6 +587,7 @@ struct sched_dl_entity { · 586 unmodified lines
587587 * @value: clamp value "assigned" to a se
588588 * @bucket_id: bucket index corresponding to the "assigned" value
589589 * @active: the se is currently refcounted in a rq's bucket
590+ * @user_defined: the requested clamp value comes from user-space
590591 *
591592 * The bucket_id is the index of the clamp bucket matching the clamp value
592593 * which is pre-computed and stored to avoid expensive integer divisions from
@@ -596,11 +597,19 @@ struct sched_dl_entity { · 3 unmodified lines
596597 * which can be different from the clamp value "requested" from user-space.
597598 * This allows to know a task is refcounted in the rq's bucket corresponding
598599 * to the "effective" bucket_id.
600+ *
601+ * The user_defined bit is set whenever a task has got a task-specific clamp
602+ * value requested from userspace, i.e. the system defaults apply to this task
603+ * just as a restriction. This allows to relax default clamps when a less
604+ * restrictive task-specific value has been requested, thus allowing to
605+ * implement a "nice" semantic. For example, a task running with a 20%
606+ * default boost can still drop its own boosting to 0%.
599607 */
600608 struct uclamp_se {
601609 unsigned int value : bits_per(SCHED_CAPACITY_SCALE);
602610 unsigned int bucket_id : bits_per(UCLAMP_BUCKETS);
603611 unsigned int active : 1;
612+ unsigned int user_defined : 1;
604613 };
605614 #endif /* CONFIG_UCLAMP_TASK */
606615
include/uapi/linux/sched.h+11−1
@@ -52,10 +52,20 @@ · 51 unmodified lines
5252 #define SCHED_FLAG_RECLAIM 0x02
5353 #define SCHED_FLAG_DL_OVERRUN 0x04
5454 #define SCHED_FLAG_KEEP_POLICY 0x08
55+#define SCHED_FLAG_KEEP_PARAMS 0x10
56+#define SCHED_FLAG_UTIL_CLAMP_MIN 0x20
57+#define SCHED_FLAG_UTIL_CLAMP_MAX 0x40
58+
59+#define SCHED_FLAG_KEEP_ALL (SCHED_FLAG_KEEP_POLICY | \
60+ SCHED_FLAG_KEEP_PARAMS)
61+
62+#define SCHED_FLAG_UTIL_CLAMP (SCHED_FLAG_UTIL_CLAMP_MIN | \
63+ SCHED_FLAG_UTIL_CLAMP_MAX)
5564
5665 #define SCHED_FLAG_ALL (SCHED_FLAG_RESET_ON_FORK | \
5766 SCHED_FLAG_RECLAIM | \
5867 SCHED_FLAG_DL_OVERRUN | \
59− SCHED_FLAG_KEEP_POLICY)
68+ SCHED_FLAG_KEEP_ALL | \
69+ SCHED_FLAG_UTIL_CLAMP)
6070
6171 #endif /* _UAPI_LINUX_SCHED_H */
include/uapi/linux/sched/types.h+57−9
@@ -9,6 +9,7 @@ struct sched_param { · 8 unmodified lines
99 };
1010
1111 #define SCHED_ATTR_SIZE_VER0 48 /* sizeof first published struct */
12+#define SCHED_ATTR_SIZE_VER1 56 /* add: util_{min,max} */
1213
1314 /*
1415 * Extended scheduling parameters data structure.
@@ -21,8 +22,33 @@ struct sched_param { · 6 unmodified lines
2122 * the tasks may be useful for a wide variety of application fields, e.g.,
2223 * multimedia, streaming, automation and control, and many others.
2324 *
24− * This variant (sched_attr) is meant at describing a so-called
25− * sporadic time-constrained task. In such model a task is specified by:
25+ * This variant (sched_attr) allows to define additional attributes to
26+ * improve the scheduler knowledge about task requirements.
27+ *
28+ * Scheduling Class Attributes
29+ * ===========================
30+ *
31+ * A subset of sched_attr attributes specifies the
32+ * scheduling policy and relative POSIX attributes:
33+ *
34+ * @size size of the structure, for fwd/bwd compat.
35+ *
36+ * @sched_policy task's scheduling policy
37+ * @sched_nice task's nice value (SCHED_NORMAL/BATCH)
38+ * @sched_priority task's static priority (SCHED_FIFO/RR)
39+ *
40+ * Certain more advanced scheduling features can be controlled by a
41+ * predefined set of flags via the attribute:
42+ *
43+ * @sched_flags for customizing the scheduler behaviour
44+ *
45+ * Sporadic Time-Constrained Task Attributes
46+ * =========================================
47+ *
48+ * A subset of sched_attr attributes allows to describe a so-called
49+ * sporadic time-constrained task.
50+ *
51+ * In such a model a task is specified by:
2652 * - the activation period or minimum instance inter-arrival time;
2753 * - the maximum (or average, depending on the actual scheduling
2854 * discipline) computation time of all instances, a.k.a. runtime;
@@ -34,14 +60,8 @@ struct sched_param { · 5 unmodified lines
3460 * than the runtime and must be completed by time instant t equal to
3561 * the instance activation time + the deadline.
3662 *
37− * This is reflected by the actual fields of the sched_attr structure:
63+ * This is reflected by the following fields of the sched_attr structure:
3864 *
39− * @size size of the structure, for fwd/bwd compat.
40− *
41− * @sched_policy task's scheduling policy
42− * @sched_flags for customizing the scheduler behaviour
43− * @sched_nice task's nice value (SCHED_NORMAL/BATCH)
44− * @sched_priority task's static priority (SCHED_FIFO/RR)
4565 * @sched_deadline representative of the task's deadline
4666 * @sched_runtime representative of the task's runtime
4767 * @sched_period representative of the task's period
@@ -53,6 +73,29 @@ struct sched_param { · 5 unmodified lines
5373 * As of now, the SCHED_DEADLINE policy (sched_dl scheduling class) is the
5474 * only user of this new interface. More information about the algorithm
5575 * available in the scheduling class file or in Documentation/.
76+ *
77+ * Task Utilization Attributes
78+ * ===========================
79+ *
80+ * A subset of sched_attr attributes allows to specify the utilization
81+ * expected for a task. These attributes allow to inform the scheduler about
82+ * the utilization boundaries within which it should schedule the task. These
83+ * boundaries are valuable hints to support scheduler decisions on both task
84+ * placement and frequency selection.
85+ *
86+ * @sched_util_min represents the minimum utilization
87+ * @sched_util_max represents the maximum utilization
88+ *
89+ * Utilization is a value in the range [0..SCHED_CAPACITY_SCALE]. It
90+ * represents the percentage of CPU time used by a task when running at the
91+ * maximum frequency on the highest capacity CPU of the system. For example, a
92+ * 20% utilization task is a task running for 2ms every 10ms at maximum
93+ * frequency.
94+ *
95+ * A task with a min utilization value bigger than 0 is more likely scheduled
96+ * on a CPU with a capacity big enough to fit the specified value.
97+ * A task with a max utilization value smaller than 1024 is more likely
98+ * scheduled on a CPU with no more capacity than the specified value.
5699 */
57100 struct sched_attr {
58101 __u32 size;
@@ -70,6 +113,11 @@ struct sched_attr { · 11 unmodified lines
70113 __u64 sched_runtime;
71114 __u64 sched_deadline;
72115 __u64 sched_period;
116+
117+ /* Utilization hints */
118+ __u32 sched_util_min;
119+ __u32 sched_util_max;
120+
73121 };
74122
75123 #endif /* _UAPI_LINUX_SCHED_TYPES_H */
kernel/sched/core.c+84−7
@@ -805,10 +805,12 @@ static inline unsigned int uclamp_none(int clamp_id) · 804 unmodified lines
805805 return SCHED_CAPACITY_SCALE;
806806 }
807807
808−static inline void uclamp_se_set(struct uclamp_se *uc_se, unsigned int value)
808+static inline void uclamp_se_set(struct uclamp_se *uc_se,
809+ unsigned int value, bool user_defined)
809810 {
810811 uc_se->value = value;
811812 uc_se->bucket_id = uclamp_bucket_id(value);
813+ uc_se->user_defined = user_defined;
812814 }
813815
814816 static inline unsigned int
@@ -1016,11 +1018,11 @@ int sysctl_sched_uclamp_handler(struct ctl_table *table, int write, · 201 unmodified lines
10161018
10171019 if (old_min != sysctl_sched_uclamp_util_min) {
10181020 uclamp_se_set(&uclamp_default[UCLAMP_MIN],
1019− sysctl_sched_uclamp_util_min);
1021+ sysctl_sched_uclamp_util_min, false);
10201022 }
10211023 if (old_max != sysctl_sched_uclamp_util_max) {
10221024 uclamp_se_set(&uclamp_default[UCLAMP_MAX],
1023− sysctl_sched_uclamp_util_max);
1025+ sysctl_sched_uclamp_util_max, false);
10241026 }
10251027
10261028 /*
@@ -1038,6 +1040,42 @@ done: · 11 unmodified lines
10381040 return result;
10391041 }
10401042
1043+static int uclamp_validate(struct task_struct *p,
1044+ const struct sched_attr *attr)
1045+{
1046+ unsigned int lower_bound = p->uclamp_req[UCLAMP_MIN].value;
1047+ unsigned int upper_bound = p->uclamp_req[UCLAMP_MAX].value;
1048+
1049+ if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP_MIN)
1050+ lower_bound = attr->sched_util_min;
1051+ if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP_MAX)
1052+ upper_bound = attr->sched_util_max;
1053+
1054+ if (lower_bound > upper_bound)
1055+ return -EINVAL;
1056+ if (upper_bound > SCHED_CAPACITY_SCALE)
1057+ return -EINVAL;
1058+
1059+ return 0;
1060+}
1061+
1062+static void __setscheduler_uclamp(struct task_struct *p,
1063+ const struct sched_attr *attr)
1064+{
1065+ if (likely(!(attr->sched_flags & SCHED_FLAG_UTIL_CLAMP)))
1066+ return;
1067+
1068+ if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP_MIN) {
1069+ uclamp_se_set(&p->uclamp_req[UCLAMP_MIN],
1070+ attr->sched_util_min, true);
1071+ }
1072+
1073+ if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP_MAX) {
1074+ uclamp_se_set(&p->uclamp_req[UCLAMP_MAX],
1075+ attr->sched_util_max, true);
1076+ }
1077+}
1078+
10411079 static void uclamp_fork(struct task_struct *p)
10421080 {
10431081 unsigned int clamp_id;
@@ -1059,11 +1097,11 @@ static void __init init_uclamp(void) · 15 unmodified lines
10591097
10601098 for_each_clamp_id(clamp_id) {
10611099 uclamp_se_set(&init_task.uclamp_req[clamp_id],
1062− uclamp_none(clamp_id));
1100+ uclamp_none(clamp_id), false);
10631101 }
10641102
10651103 /* System defaults allow max clamp values for both indexes */
1066− uclamp_se_set(&uc_max, uclamp_none(UCLAMP_MAX));
1104+ uclamp_se_set(&uc_max, uclamp_none(UCLAMP_MAX), false);
10671105 for_each_clamp_id(clamp_id)
10681106 uclamp_default[clamp_id] = uc_max;
10691107 }
@@ -1071,6 +1109,13 @@ static void __init init_uclamp(void) · 1 unmodified line
10711109 #else /* CONFIG_UCLAMP_TASK */
10721110 static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p) { }
10731111 static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p) { }
1112+static inline int uclamp_validate(struct task_struct *p,
1113+ const struct sched_attr *attr)
1114+{
1115+ return -EOPNOTSUPP;
1116+}
1117+static void __setscheduler_uclamp(struct task_struct *p,
1118+ const struct sched_attr *attr) { }
10741119 static inline void uclamp_fork(struct task_struct *p) { }
10751120 static inline void init_uclamp(void) { }
10761121 #endif /* CONFIG_UCLAMP_TASK */
@@ -4412,6 +4457,13 @@ static void __setscheduler_params(struct task_struct *p, · 3335 unmodified lines
44124457 static void __setscheduler(struct rq *rq, struct task_struct *p,
44134458 const struct sched_attr *attr, bool keep_boost)
44144459 {
4460+ /*
4461+ * If params can't change scheduling class changes aren't allowed
4462+ * either.
4463+ */
4464+ if (attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)
4465+ return;
4466+
44154467 __setscheduler_params(p, attr);
44164468
44174469 /*
@@ -4549,6 +4601,13 @@ recheck: · 131 unmodified lines
45494601 return retval;
45504602 }
45514603
4604+ /* Update task specific "requested" clamps */
4605+ if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP) {
4606+ retval = uclamp_validate(p, attr);
4607+ if (retval)
4608+ return retval;
4609+ }
4610+
45524611 /*
45534612 * Make sure no PI-waiters arrive (or leave) while we are
45544613 * changing the priority of the task:
@@ -4578,6 +4637,8 @@ recheck: · 23 unmodified lines
45784637 goto change;
45794638 if (dl_policy(policy) && dl_param_changed(p, attr))
45804639 goto change;
4640+ if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP)
4641+ goto change;
45814642
45824643 p->sched_reset_on_fork = reset_on_fork;
45834644 task_rq_unlock(rq, p, &rf);
@@ -4658,7 +4719,9 @@ change: · 74 unmodified lines
46584719 put_prev_task(rq, p);
46594720
46604721 prev_class = p->sched_class;
4722+
46614723 __setscheduler(rq, p, attr, pi);
4724+ __setscheduler_uclamp(p, attr);
46624725
46634726 if (queued) {
46644727 /*
@@ -4834,6 +4897,10 @@ static int sched_copy_attr(struct sched_attr __user *uattr, struct sched_attr *a · 169 unmodified lines
48344897 if (ret)
48354898 return -EFAULT;
48364899
4900+ if ((attr->sched_flags & SCHED_FLAG_UTIL_CLAMP) &&
4901+ size < SCHED_ATTR_SIZE_VER1)
4902+ return -EINVAL;
4903+
48374904 /*
48384905 * XXX: Do we want to be lenient like existing syscalls; or do we want
48394906 * to be strict and return an error on out-of-bounds values?
@@ -4903,10 +4970,15 @@ SYSCALL_DEFINE3(sched_setattr, pid_t, pid, struct sched_attr __user *, uattr, · 63 unmodified lines
49034970 rcu_read_lock();
49044971 retval = -ESRCH;
49054972 p = find_process_by_pid(pid);
4906− if (p != NULL)
4907− retval = sched_setattr(p, &attr);
4973+ if (likely(p))
4974+ get_task_struct(p);
49084975 rcu_read_unlock();
49094976
4977+ if (likely(p)) {
4978+ retval = sched_setattr(p, &attr);
4979+ put_task_struct(p);
4980+ }
4981+
49104982 return retval;
49114983 }
49124984
@@ -5057,6 +5129,11 @@ SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr, · 144 unmodified lines
50575129 else
50585130 attr.sched_nice = task_nice(p);
50595131
5132+#ifdef CONFIG_UCLAMP_TASK
5133+ attr.sched_util_min = p->uclamp_req[UCLAMP_MIN].value;
5134+ attr.sched_util_max = p->uclamp_req[UCLAMP_MAX].value;
5135+#endif
5136+
50605137 rcu_read_unlock();
50615138
50625139 retval = sched_read_attr(uattr, &attr, size);