요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
CBS의 deadline과 runtime 갱신
sched-deadline.rst:47-125| 값 | 의미 | 갱신 시점 |
|---|---|---|
| runtime | 한 period에서 사용할 수 있는 CPU 시간 | 사용자가 sched_attr로 설정 |
| deadline | period 시작점부터 완료해야 하는 상대 시간 | wake-up 때 절대 deadline 계산에 사용 |
| period | 예약 대역폭을 다시 공급하는 주기 | replenishment 때 다음 주기로 이동 |
| remaining runtime | 현재 예약에서 남은 실행 시간 | 실행한 시간만큼 감소 |
| scheduling deadline | EDF 비교에 쓰는 절대 deadline | wake-up 또는 replenishment 때 갱신 |
wake-up 시점에는 남은 runtime을 기존 deadline까지의 남은 시간으로 나눈 값과 runtime/period를 비교합니다. 기존 예약을 그대로 유지했을 때 순간 대역폭이 예약 비율보다 커지거나 deadline이 이미 지났다면 새 deadline을 current time + deadline으로 잡고 runtime을 다시 채웁니다.
remaining runtime이 0 이하가 되면 task는 throttled 상태가 됩니다. 이 task는 replenishment 시각 전에는 runnable이어도 선택되지 않습니다. replenishment에서는 scheduling deadline에 period를 더하고 remaining runtime에 runtime을 더해 다음 예약을 준비합니다.
SCHED_FLAG_DL_OVERRUN을 설정하면 runtime 초과 사실을 SIGXCPU로 통지받을 수 있습니다. 신호의 수신 주체와 실제 초과를 일으킨 thread를 연결할 때는 process-directed signal이라는 제한을 함께 고려해야 합니다.
GRUB 상태와 0-lag 시각
sched-deadline.rst:126-224block 직후에는 예약 대역폭을 곧바로 회수하지 않습니다. 0-lag 시각까지 Active Non-Contending으로 남겨 실시간 보장을 유지하고, timer가 만료된 뒤에만 Inactive 대역폭으로 회수합니다.
Active Contending은 실행 중이거나 실행 가능한 상태입니다. task가 block되면 즉시 Inactive가 되지 않고 Active Non-Contending으로 이동합니다. 남은 runtime이 있는데 대역폭을 즉시 회수하면 task가 다시 깨어났을 때 원래 보장을 지킬 수 없기 때문입니다.
0-lag 시각은 deadline - (remaining runtime × dl_period / dl_runtime)으로 계산합니다. 그 시각까지 task가 깨어나지 않으면 inactive timer가 running_bw에서 해당 utilization을 제거합니다. 먼저 깨어나면 timer를 취소하고 다시 Active Contending으로 돌아갑니다.
| runqueue 값 | 포함 범위 | 용도 |
|---|---|---|
| running_bw | Active Contending + Active Non-Contending | 현재 보장해야 하는 활성 대역폭 |
| this_bw | Inactive를 포함해 runqueue에 속한 모든 deadline task | 전체 예약량 |
| max_bw | deadline class가 쓸 수 있는 최대 대역폭 | RT throttling 한계 반영 |
두 task가 대역폭을 회수하는 시간축
sched-deadline.rst:225-289t=0에는 두 예약이 모두 활성입니다. T1이 t=2에 block되어도 t=4까지 대역폭은 유지됩니다. t=4 이후 T1이 Inactive가 되면 T2가 그 대역폭을 회수합니다.
회수는 실행 task의 runtime 차감 속도를 조정하는 방식입니다. inactive utilization이 커질수록 실행 task의 예산은 실제 시간보다 느리게 줄어듭니다. 따라서 새로운 CPU 시간을 만드는 것이 아니라 이미 예약되었으나 쓰이지 않는 시간을 다른 deadline task가 빌려 쓰는 구조입니다.
실시간 task 모델과 schedulability
sched-deadline.rst:303-551주기 또는 sporadic task의 각 job은 arrival time r, computation time c, absolute deadline d로 기술합니다. WCET는 모든 job의 c 중 최댓값이며 상대 deadline D는 d = r + D 관계로 연결됩니다. 커널의 runtime은 보장하려는 실행 예산, period는 최소 도착 간격, deadline은 상대 deadline에 대응합니다.
단일 CPU에서 deadline과 period가 같은 implicit-deadline task는 utilization 합이 1 이하이면 EDF로 스케줄 가능합니다. deadline이 period보다 짧은 constrained-deadline task는 단순 utilization만으로 충분하지 않으며 demand bound function으로 모든 구간의 요구량을 검사해야 합니다.
다중 CPU에서는 global EDF의 migration 비용과 partitioned EDF의 bin-packing 문제가 추가됩니다. Linux는 root domain과 CPU affinity 범위 안에서 deadline bandwidth admission을 수행하므로 cpuset 또는 affinity 변경도 예약 가능성 판정에 영향을 줍니다.
| 이론 값 | SCHED_DEADLINE 값 | 주의점 |
|---|---|---|
| WCET | runtime | 측정 평균이 아니라 보장할 최악 실행량을 기준으로 잡아야 함 |
| relative deadline D | deadline | 일반적으로 runtime ≤ deadline |
| minimum inter-arrival P | period | 일반적으로 deadline ≤ period |
| utilization C/P | runtime/period | admission control이 합계를 제한 |
대역폭 관리와 사용자 인터페이스
sched-deadline.rst:552-671시스템 전체 deadline 대역폭은 /proc/sys/kernel/sched_rt_period_us와 sched_rt_runtime_us의 영향을 받습니다. runtime을 -1로 두면 RT throttling을 끄지만 일반 task starvation을 막는 보호막도 사라집니다.
task별 속성은 sched_setattr()와 sched_getattr()로 설정하고 읽습니다. sched_policy를 SCHED_DEADLINE으로 지정한 뒤 sched_runtime, sched_deadline, sched_period를 나노초 단위로 제공합니다. 커널은 기본 관계와 admission 조건을 검증하고 불가능한 예약은 EINVAL 또는 EBUSY로 거부합니다.
sched_yield()는 남은 runtime을 단순히 다른 task에게 양도하는 일반 yield와 의미가 다릅니다. deadline task가 호출하면 현재 job을 완료한 것으로 취급하여 다음 period까지 실행되지 않을 수 있으므로 반복문 안의 관습적인 yield 용도로 사용하면 안 됩니다.
CPU affinity, cpuset과 시험 방법
sched-deadline.rst:672-920deadline task를 특정 CPU 집합에 묶으려면 그 집합의 root domain에서 admission이 성립해야 합니다. cgroup v1 cpuset과 cgroup v2 cpuset partition은 root domain을 나누는 방법과 활성화 순서가 다르므로 문서의 절차대로 CPU와 memory node를 배치해야 합니다.
시험은 kernel selftests의 sched deadline 항목과 cyclictest 같은 latency 도구를 함께 사용합니다. 단순히 deadline miss 개수만 보지 말고 runtime depletion, throttling, migration, cpufreq 전환 지연과 IRQ 간섭을 tracepoint로 확인해야 원인을 분리할 수 있습니다.
부록의 최소 C 프로그램은 sched_setattr() syscall wrapper와 주기 실행 loop를 보여 주는 출발점입니다. 실제 제품 코드에서는 mlockall(), page fault 사전 제거, clock source 선택, error path와 권한 설정까지 추가해야 합니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
========================
Deadline Task Scheduling
========================
.. CONTENTS
0. WARNING
1. Overview
2. Scheduling algorithm
2.1 Main algorithm
2.2 Bandwidth reclaiming
3. Scheduling Real-Time Tasks
3.1 Definitions
3.2 Schedulability Analysis for Uniprocessor Systems
3.3 Schedulability Analysis for Multiprocessor Systems
3.4 Relationship with SCHED_DEADLINE Parameters
4. Bandwidth management
4.1 System-wide settings
4.2 Task interface
4.3 Default behavior
4.4 Behavior of sched_yield()
5. Tasks CPU affinity
5.1 Using cgroup v1 cpuset controller
5.2 Using cgroup v2 cpuset controller
6. Future plans
A. Test suite
B. Minimal main()
0. WARNING
==========
Fiddling with these settings can result in an unpredictable or even unstable
system behavior. As for -rt (group) scheduling, it is assumed that root users
know what they're doing.
1. Overview
===========
The SCHED_DEADLINE policy contained inside the sched_dl scheduling class is
basically an implementation of the Earliest Deadline First (EDF) scheduling
algorithm, augmented with a mechanism (called Constant Bandwidth Server, CBS)
that makes it possible to isolate the behavior of tasks between each other.
2. Scheduling algorithm
=======================
2.1 Main algorithm
------------------
SCHED_DEADLINE [18] uses three parameters, named "runtime", "period", and
"deadline", to schedule tasks. A SCHED_DEADLINE task should receive
"runtime" microseconds of execution time every "period" microseconds, and
these "runtime" microseconds are available within "deadline" microseconds
from the beginning of the period. In order to implement this behavior,
every time the task wakes up, the scheduler computes a "scheduling deadline"
consistent with the guarantee (using the CBS[2,3] algorithm). Tasks are then
scheduled using EDF[1] on these scheduling deadlines (the task with the
earliest scheduling deadline is selected for execution). Notice that the
task actually receives "runtime" time units within "deadline" if a proper
"admission control" strategy (see Section "4. Bandwidth management") is used
(clearly, if the system is overloaded this guarantee cannot be respected).
Summing up, the CBS[2,3] algorithm assigns scheduling deadlines to tasks so
that each task runs for at most its runtime every period, avoiding any
interference between different tasks (bandwidth isolation), while the EDF[1]
algorithm selects the task with the earliest scheduling deadline as the one
to be executed next. Thanks to this feature, tasks that do not strictly comply
with the "traditional" real-time task model (see Section 3) can effectively
use the new policy.
In more details, the CBS algorithm assigns scheduling deadlines to
tasks in the following way:
- Each SCHED_DEADLINE task is characterized by the "runtime",
"deadline", and "period" parameters;
- The state of the task is described by a "scheduling deadline", and
a "remaining runtime". These two parameters are initially set to 0;
- When a SCHED_DEADLINE task wakes up (becomes ready for execution),
the scheduler checks if::
remaining runtime runtime
---------------------------------- > ---------
scheduling deadline - current time period
then, if the scheduling deadline is smaller than the current time, or
this condition is verified, the scheduling deadline and the
remaining runtime are re-initialized as
scheduling deadline = current time + deadline
remaining runtime = runtime
otherwise, the scheduling deadline and the remaining runtime are
left unchanged;
- When a SCHED_DEADLINE task executes for an amount of time t, its
remaining runtime is decreased as::
remaining runtime = remaining runtime - t
(technically, the runtime is decreased at every tick, or when the
task is descheduled / preempted);
- When the remaining runtime becomes less or equal than 0, the task is
said to be "throttled" (also known as "depleted" in real-time literature)
and cannot be scheduled until its scheduling deadline. The "replenishment
time" for this task (see next item) is set to be equal to the current
value of the scheduling deadline;
- When the current time is equal to the replenishment time of a
throttled task, the scheduling deadline and the remaining runtime are
updated as::
scheduling deadline = scheduling deadline + period
remaining runtime = remaining runtime + runtime
The SCHED_FLAG_DL_OVERRUN flag in sched_attr's sched_flags field allows a task
to get informed about runtime overruns through the delivery of SIGXCPU
signals.
2.2 Bandwidth reclaiming
------------------------
Bandwidth reclaiming for deadline tasks is based on the GRUB (Greedy
Reclamation of Unused Bandwidth) algorithm [15, 16, 17] and it is enabled
when flag SCHED_FLAG_RECLAIM is set.
The following diagram illustrates the state names for tasks handled by GRUB::
------------
(d) | Active |
------------->| |
| | Contending |
| ------------
| A |
---------- | |
| | | |
| Inactive | |(b) | (a)
| | | |
---------- | |
A | V
| ------------
| | Active |
--------------| Non |
(c) | Contending |
------------
A task can be in one of the following states:
- ActiveContending: if it is ready for execution (or executing);
- ActiveNonContending: if it just blocked and has not yet surpassed the 0-lag
time;
- Inactive: if it is blocked and has surpassed the 0-lag time.
State transitions:
(a) When a task blocks, it does not become immediately inactive since its
bandwidth cannot be immediately reclaimed without breaking the
real-time guarantees. It therefore enters a transitional state called
ActiveNonContending. The scheduler arms the "inactive timer" to fire at
the 0-lag time, when the task's bandwidth can be reclaimed without
breaking the real-time guarantees.
The 0-lag time for a task entering the ActiveNonContending state is
computed as::
(runtime * dl_period)
deadline - ---------------------
dl_runtime
where runtime is the remaining runtime, while dl_runtime and dl_period
are the reservation parameters.
(b) If the task wakes up before the inactive timer fires, the task re-enters
the ActiveContending state and the "inactive timer" is canceled.
In addition, if the task wakes up on a different runqueue, then
the task's utilization must be removed from the previous runqueue's active
utilization and must be added to the new runqueue's active utilization.
In order to avoid races between a task waking up on a runqueue while the
"inactive timer" is running on a different CPU, the "dl_non_contending"
flag is used to indicate that a task is not on a runqueue but is active
(so, the flag is set when the task blocks and is cleared when the
"inactive timer" fires or when the task wakes up).
(c) When the "inactive timer" fires, the task enters the Inactive state and
its utilization is removed from the runqueue's active utilization.
(d) When an inactive task wakes up, it enters the ActiveContending state and
its utilization is added to the active utilization of the runqueue where
it has been enqueued.
For each runqueue, the algorithm GRUB keeps track of two different bandwidths:
- Active bandwidth (running_bw): this is the sum of the bandwidths of all
tasks in active state (i.e., ActiveContending or ActiveNonContending);
- Total bandwidth (this_bw): this is the sum of all tasks "belonging" to the
runqueue, including the tasks in Inactive state.
- Maximum usable bandwidth (max_bw): This is the maximum bandwidth usable by
deadline tasks and is currently set to the RT capacity.
The algorithm reclaims the bandwidth of the tasks in Inactive state.
It does so by decrementing the runtime of the executing task Ti at a pace equal
to
dq = -(max{ Ui, (Umax - Uinact - Uextra) } / Umax) dt
where:
- Ui is the bandwidth of task Ti;
- Umax is the maximum reclaimable utilization (subjected to RT throttling
limits);
- Uinact is the (per runqueue) inactive utilization, computed as
(this_bq - running_bw);
- Uextra is the (per runqueue) extra reclaimable utilization
(subjected to RT throttling limits).
Let's now see a trivial example of two deadline tasks with runtime equal
to 4 and period equal to 8 (i.e., bandwidth equal to 0.5)::
A Task T1
|
| |
| |
|-------- |----
| | V
|---|---|---|---|---|---|---|---|--------->t
0 1 2 3 4 5 6 7 8
A Task T2
|
| |
| |
| ------------------------|
| | V
|---|---|---|---|---|---|---|---|--------->t
0 1 2 3 4 5 6 7 8
A running_bw
|
1 ----------------- ------
| | |
0.5- -----------------
| |
|---|---|---|---|---|---|---|---|--------->t
0 1 2 3 4 5 6 7 8
- Time t = 0:
Both tasks are ready for execution and therefore in ActiveContending state.
Suppose Task T1 is the first task to start execution.
Since there are no inactive tasks, its runtime is decreased as dq = -1 dt.
- Time t = 2:
Suppose that task T1 blocks
Task T1 therefore enters the ActiveNonContending state. Since its remaining
runtime is equal to 2, its 0-lag time is equal to t = 4.
Task T2 start execution, with runtime still decreased as dq = -1 dt since
there are no inactive tasks.
- Time t = 4:
This is the 0-lag time for Task T1. Since it didn't woken up in the
meantime, it enters the Inactive state. Its bandwidth is removed from
running_bw.
Task T2 continues its execution. However, its runtime is now decreased as
dq = - 0.5 dt because Uinact = 0.5.
Task T2 therefore reclaims the bandwidth unused by Task T1.
- Time t = 8:
Task T1 wakes up. It enters the ActiveContending state again, and the
running_bw is incremented.
2.3 Energy-aware scheduling
---------------------------
When cpufreq's schedutil governor is selected, SCHED_DEADLINE implements the
GRUB-PA [19] algorithm, reducing the CPU operating frequency to the minimum
value that still allows to meet the deadlines. This behavior is currently
implemented only for ARM architectures.
A particular care must be taken in case the time needed for changing frequency
is of the same order of magnitude of the reservation period. In such cases,
setting a fixed CPU frequency results in a lower amount of deadline misses.
3. Scheduling Real-Time Tasks
=============================
.. BIG FAT WARNING ******************************************************
.. warning::
This section contains a (not-thorough) summary on classical deadline
scheduling theory, and how it applies to SCHED_DEADLINE.
The reader can "safely" skip to Section 4 if only interested in seeing
how the scheduling policy can be used. Anyway, we strongly recommend
to come back here and continue reading (once the urge for testing is
satisfied :P) to be sure of fully understanding all technical details.
.. ************************************************************************
There are no limitations on what kind of task can exploit this new
scheduling discipline, even if it must be said that it is particularly
suited for periodic or sporadic real-time tasks that need guarantees on their
timing behavior, e.g., multimedia, streaming, control applications, etc.
3.1 Definitions
------------------------
A typical real-time task is composed of a repetition of computation phases
(task instances, or jobs) which are activated on a periodic or sporadic
fashion.
Each job J_j (where J_j is the j^th job of the task) is characterized by an
arrival time r_j (the time when the job starts), an amount of computation
time c_j needed to finish the job, and a job absolute deadline d_j, which
is the time within which the job should be finished. The maximum execution
time max{c_j} is called "Worst Case Execution Time" (WCET) for the task.
A real-time task can be periodic with period P if r_{j+1} = r_j + P, or
sporadic with minimum inter-arrival time P is r_{j+1} >= r_j + P. Finally,
d_j = r_j + D, where D is the task's relative deadline.
Summing up, a real-time task can be described as
Task = (WCET, D, P)
The utilization of a real-time task is defined as the ratio between its
WCET and its period (or minimum inter-arrival time), and represents
the fraction of CPU time needed to execute the task.
If the total utilization U=sum(WCET_i/P_i) is larger than M (with M equal
to the number of CPUs), then the scheduler is unable to respect all the
deadlines.
Note that total utilization is defined as the sum of the utilizations
WCET_i/P_i over all the real-time tasks in the system. When considering
multiple real-time tasks, the parameters of the i-th task are indicated
with the "_i" suffix.
Moreover, if the total utilization is larger than M, then we risk starving
non- real-time tasks by real-time tasks.
If, instead, the total utilization is smaller than M, then non real-time
tasks will not be starved and the system might be able to respect all the
deadlines.
As a matter of fact, in this case it is possible to provide an upper bound
for tardiness (defined as the maximum between 0 and the difference
between the finishing time of a job and its absolute deadline).
More precisely, it can be proven that using a global EDF scheduler the
maximum tardiness of each task is smaller or equal than
((M − 1) · WCET_max − WCET_min)/(M − (M − 2) · U_max) + WCET_max
where WCET_max = max{WCET_i} is the maximum WCET, WCET_min=min{WCET_i}
is the minimum WCET, and U_max = max{WCET_i/P_i} is the maximum
utilization[12].
3.2 Schedulability Analysis for Uniprocessor Systems
----------------------------------------------------
If M=1 (uniprocessor system), or in case of partitioned scheduling (each
real-time task is statically assigned to one and only one CPU), it is
possible to formally check if all the deadlines are respected.
If D_i = P_i for all tasks, then EDF is able to respect all the deadlines
of all the tasks executing on a CPU if and only if the total utilization
of the tasks running on such a CPU is smaller or equal than 1.
If D_i != P_i for some task, then it is possible to define the density of
a task as WCET_i/min{D_i,P_i}, and EDF is able to respect all the deadlines
of all the tasks running on a CPU if the sum of the densities of the tasks
running on such a CPU is smaller or equal than 1:
sum(WCET_i / min{D_i, P_i}) <= 1
It is important to notice that this condition is only sufficient, and not
necessary: there are task sets that are schedulable, but do not respect the
condition. For example, consider the task set {Task_1,Task_2} composed by
Task_1=(50ms,50ms,100ms) and Task_2=(10ms,100ms,100ms).
EDF is clearly able to schedule the two tasks without missing any deadline
(Task_1 is scheduled as soon as it is released, and finishes just in time
to respect its deadline; Task_2 is scheduled immediately after Task_1, hence
its response time cannot be larger than 50ms + 10ms = 60ms) even if
50 / min{50,100} + 10 / min{100, 100} = 50 / 50 + 10 / 100 = 1.1
Of course it is possible to test the exact schedulability of tasks with
D_i != P_i (checking a condition that is both sufficient and necessary),
but this cannot be done by comparing the total utilization or density with
a constant. Instead, the so called "processor demand" approach can be used,
computing the total amount of CPU time h(t) needed by all the tasks to
respect all of their deadlines in a time interval of size t, and comparing
such a time with the interval size t. If h(t) is smaller than t (that is,
the amount of time needed by the tasks in a time interval of size t is
smaller than the size of the interval) for all the possible values of t, then
EDF is able to schedule the tasks respecting all of their deadlines. Since
performing this check for all possible values of t is impossible, it has been
proven[4,5,6] that it is sufficient to perform the test for values of t
between 0 and a maximum value L. The cited papers contain all of the
mathematical details and explain how to compute h(t) and L.
In any case, this kind of analysis is too complex as well as too
time-consuming to be performed on-line. Hence, as explained in Section
4 Linux uses an admission test based on the tasks' utilizations.
3.3 Schedulability Analysis for Multiprocessor Systems
------------------------------------------------------
On multiprocessor systems with global EDF scheduling (non partitioned
systems), a sufficient test for schedulability can not be based on the
utilizations or densities: it can be shown that even if D_i = P_i task
sets with utilizations slightly larger than 1 can miss deadlines regardless
of the number of CPUs.
Consider a set {Task_1,...Task_{M+1}} of M+1 tasks on a system with M
CPUs, with the first task Task_1=(P,P,P) having period, relative deadline
and WCET equal to P. The remaining M tasks Task_i=(e,P-1,P-1) have an
arbitrarily small worst case execution time (indicated as "e" here) and a
period smaller than the one of the first task. Hence, if all the tasks
activate at the same time t, global EDF schedules these M tasks first
(because their absolute deadlines are equal to t + P - 1, hence they are
smaller than the absolute deadline of Task_1, which is t + P). As a
result, Task_1 can be scheduled only at time t + e, and will finish at
time t + e + P, after its absolute deadline. The total utilization of the
task set is U = M · e / (P - 1) + P / P = M · e / (P - 1) + 1, and for small
values of e this can become very close to 1. This is known as "Dhall's
effect"[7]. Note: the example in the original paper by Dhall has been
slightly simplified here (for example, Dhall more correctly computed
lim_{e->0}U).
More complex schedulability tests for global EDF have been developed in
real-time literature[8,9], but they are not based on a simple comparison
between total utilization (or density) and a fixed constant. If all tasks
have D_i = P_i, a sufficient schedulability condition can be expressed in
a simple way:
sum(WCET_i / P_i) <= M - (M - 1) · U_max
where U_max = max{WCET_i / P_i}[10]. Notice that for U_max = 1,
M - (M - 1) · U_max becomes M - M + 1 = 1 and this schedulability condition
just confirms the Dhall's effect. A more complete survey of the literature
about schedulability tests for multi-processor real-time scheduling can be
found in [11].
As seen, enforcing that the total utilization is smaller than M does not
guarantee that global EDF schedules the tasks without missing any deadline
(in other words, global EDF is not an optimal scheduling algorithm). However,
a total utilization smaller than M is enough to guarantee that non real-time
tasks are not starved and that the tardiness of real-time tasks has an upper
bound[12] (as previously noted). Different bounds on the maximum tardiness
experienced by real-time tasks have been developed in various papers[13,14],
but the theoretical result that is important for SCHED_DEADLINE is that if
the total utilization is smaller or equal than M then the response times of
the tasks are limited.
3.4 Relationship with SCHED_DEADLINE Parameters
-----------------------------------------------
Finally, it is important to understand the relationship between the
SCHED_DEADLINE scheduling parameters described in Section 2 (runtime,
deadline and period) and the real-time task parameters (WCET, D, P)
described in this section. Note that the tasks' temporal constraints are
represented by its absolute deadlines d_j = r_j + D described above, while
SCHED_DEADLINE schedules the tasks according to scheduling deadlines (see
Section 2).
If an admission test is used to guarantee that the scheduling deadlines
are respected, then SCHED_DEADLINE can be used to schedule real-time tasks
guaranteeing that all the jobs' deadlines of a task are respected.
In order to do this, a task must be scheduled by setting:
- runtime >= WCET
- deadline = D
- period <= P
IOW, if runtime >= WCET and if period is <= P, then the scheduling deadlines
and the absolute deadlines (d_j) coincide, so a proper admission control
allows to respect the jobs' absolute deadlines for this task (this is what is
called "hard schedulability property" and is an extension of Lemma 1 of [2]).
Notice that if runtime > deadline the admission control will surely reject
this task, as it is not possible to respect its temporal constraints.
References:
1 - C. L. Liu and J. W. Layland. Scheduling algorithms for multiprogram-
ming in a hard-real-time environment. Journal of the Association for
Computing Machinery, 20(1), 1973.
2 - L. Abeni , G. Buttazzo. Integrating Multimedia Applications in Hard
Real-Time Systems. Proceedings of the 19th IEEE Real-time Systems
Symposium, 1998. http://retis.sssup.it/~giorgio/paps/1998/rtss98-cbs.pdf
3 - L. Abeni. Server Mechanisms for Multimedia Applications. ReTiS Lab
Technical Report. http://disi.unitn.it/~abeni/tr-98-01.pdf
4 - J. Y. Leung and M.L. Merril. A Note on Preemptive Scheduling of
Periodic, Real-Time Tasks. Information Processing Letters, vol. 11,
no. 3, pp. 115-118, 1980.
5 - S. K. Baruah, A. K. Mok and L. E. Rosier. Preemptively Scheduling
Hard-Real-Time Sporadic Tasks on One Processor. Proceedings of the
11th IEEE Real-time Systems Symposium, 1990.
6 - S. K. Baruah, L. E. Rosier and R. R. Howell. Algorithms and Complexity
Concerning the Preemptive Scheduling of Periodic Real-Time tasks on
One Processor. Real-Time Systems Journal, vol. 4, no. 2, pp 301-324,
1990.
7 - S. J. Dhall and C. L. Liu. On a real-time scheduling problem. Operations
research, vol. 26, no. 1, pp 127-140, 1978.
8 - T. Baker. Multiprocessor EDF and Deadline Monotonic Schedulability
Analysis. Proceedings of the 24th IEEE Real-Time Systems Symposium, 2003.
9 - T. Baker. An Analysis of EDF Schedulability on a Multiprocessor.
IEEE Transactions on Parallel and Distributed Systems, vol. 16, no. 8,
pp 760-768, 2005.
10 - J. Goossens, S. Funk and S. Baruah, Priority-Driven Scheduling of
Periodic Task Systems on Multiprocessors. Real-Time Systems Journal,
vol. 25, no. 2–3, pp. 187–205, 2003.
11 - R. Davis and A. Burns. A Survey of Hard Real-Time Scheduling for
Multiprocessor Systems. ACM Computing Surveys, vol. 43, no. 4, 2011.
http://www-users.cs.york.ac.uk/~robdavis/papers/MPSurveyv5.0.pdf
12 - U. C. Devi and J. H. Anderson. Tardiness Bounds under Global EDF
Scheduling on a Multiprocessor. Real-Time Systems Journal, vol. 32,
no. 2, pp 133-189, 2008.
13 - P. Valente and G. Lipari. An Upper Bound to the Lateness of Soft
Real-Time Tasks Scheduled by EDF on Multiprocessors. Proceedings of
the 26th IEEE Real-Time Systems Symposium, 2005.
14 - J. Erickson, U. Devi and S. Baruah. Improved tardiness bounds for
Global EDF. Proceedings of the 22nd Euromicro Conference on
Real-Time Systems, 2010.
15 - G. Lipari, S. Baruah, Greedy reclamation of unused bandwidth in
constant-bandwidth servers, 12th IEEE Euromicro Conference on Real-Time
Systems, 2000.
16 - L. Abeni, J. Lelli, C. Scordino, L. Palopoli, Greedy CPU reclaiming for
SCHED DEADLINE. In Proceedings of the Real-Time Linux Workshop (RTLWS),
Dusseldorf, Germany, 2014.
17 - L. Abeni, G. Lipari, A. Parri, Y. Sun, Multicore CPU reclaiming: parallel
or sequential?. In Proceedings of the 31st Annual ACM Symposium on Applied
Computing, 2016.
18 - J. Lelli, C. Scordino, L. Abeni, D. Faggioli, Deadline scheduling in the
Linux kernel, Software: Practice and Experience, 46(6): 821-839, June
2016.
19 - C. Scordino, L. Abeni, J. Lelli, Energy-Aware Real-Time Scheduling in
the Linux Kernel, 33rd ACM/SIGAPP Symposium On Applied Computing (SAC
2018), Pau, France, April 2018.
4. Bandwidth management
=======================
As previously mentioned, in order for -deadline scheduling to be
effective and useful (that is, to be able to provide "runtime" time units
within "deadline"), it is important to have some method to keep the allocation
of the available fractions of CPU time to the various tasks under control.
This is usually called "admission control" and if it is not performed, then
no guarantee can be given on the actual scheduling of the -deadline tasks.
As already stated in Section 3, a necessary condition to be respected to
correctly schedule a set of real-time tasks is that the total utilization
is smaller than M. When talking about -deadline tasks, this requires that
the sum of the ratio between runtime and period for all tasks is smaller
than M. Notice that the ratio runtime/period is equivalent to the utilization
of a "traditional" real-time task, and is also often referred to as
"bandwidth".
The interface used to control the CPU bandwidth that can be allocated
to -deadline tasks is similar to the one already used for -rt
tasks with real-time group scheduling (a.k.a. RT-throttling - see
Documentation/scheduler/sched-rt-group.rst), and is based on readable/
writable control files located in procfs (for system wide settings).
Notice that per-group settings (controlled through cgroupfs) are still not
defined for -deadline tasks, because more discussion is needed in order to
figure out how we want to manage SCHED_DEADLINE bandwidth at the task group
level.
A main difference between deadline bandwidth management and RT-throttling
is that -deadline tasks have bandwidth on their own (while -rt ones don't!),
and thus we don't need a higher level throttling mechanism to enforce the
desired bandwidth. In other words, this means that interface parameters are
only used at admission control time (i.e., when the user calls
sched_setattr()). Scheduling is then performed considering actual tasks'
parameters, so that CPU bandwidth is allocated to SCHED_DEADLINE tasks
respecting their needs in terms of granularity. Therefore, using this simple
interface we can put a cap on total utilization of -deadline tasks (i.e.,
\Sum (runtime_i / period_i) < global_dl_utilization_cap).
4.1 System wide settings
------------------------
The system wide settings are configured under the /proc virtual file system.
For now the -rt knobs are used for -deadline admission control and with
CONFIG_RT_GROUP_SCHED the -deadline runtime is accounted against the (root)
-rt runtime. With !CONFIG_RT_GROUP_SCHED the knob only serves for the -dl
admission control. We realize that this isn't entirely desirable; however, it
is better to have a small interface for now, and be able to change it easily
later. The ideal situation (see 5.) is to run -rt tasks from a -deadline
server; in which case the -rt bandwidth is a direct subset of dl_bw.
This means that, for a root_domain comprising M CPUs, -deadline tasks
can be created while the sum of their bandwidths stays below:
M * (sched_rt_runtime_us / sched_rt_period_us)
It is also possible to disable this bandwidth management logic, and
be thus free of oversubscribing the system up to any arbitrary level.
This is done by writing -1 in /proc/sys/kernel/sched_rt_runtime_us.
4.2 Task interface
------------------
Specifying a periodic/sporadic task that executes for a given amount of
runtime at each instance, and that is scheduled according to the urgency of
its own timing constraints needs, in general, a way of declaring:
- a (maximum/typical) instance execution time,
- a minimum interval between consecutive instances,
- a time constraint by which each instance must be completed.
Therefore:
* a new struct sched_attr, containing all the necessary fields is
provided;
* the new scheduling related syscalls that manipulate it, i.e.,
sched_setattr() and sched_getattr() are implemented.
For debugging purposes, the leftover runtime and absolute deadline of a
SCHED_DEADLINE task can be retrieved through /proc/<pid>/sched (entries
dl.runtime and dl.deadline, both values in ns). A programmatic way to
retrieve these values from production code is under discussion.
4.3 Default behavior
---------------------
The default value for SCHED_DEADLINE bandwidth is to have rt_runtime equal to
950000. With rt_period equal to 1000000, by default, it means that -deadline
tasks can use at most 95%, multiplied by the number of CPUs that compose the
root_domain, for each root_domain.
This means that non -deadline tasks will receive at least 5% of the CPU time,
and that -deadline tasks will receive their runtime with a guaranteed
worst-case delay respect to the "deadline" parameter. If "deadline" = "period"
and the cpuset mechanism is used to implement partitioned scheduling (see
Section 5), then this simple setting of the bandwidth management is able to
deterministically guarantee that -deadline tasks will receive their runtime
in a period.
Finally, notice that in order not to jeopardize the admission control a
-deadline task cannot fork.
4.4 Behavior of sched_yield()
-----------------------------
When a SCHED_DEADLINE task calls sched_yield(), it gives up its
remaining runtime and is immediately throttled, until the next
period, when its runtime will be replenished (a special flag
dl_yielded is set and used to handle correctly throttling and runtime
replenishment after a call to sched_yield()).
This behavior of sched_yield() allows the task to wake-up exactly at
the beginning of the next period. Also, this may be useful in the
future with bandwidth reclaiming mechanisms, where sched_yield() will
make the leftoever runtime available for reclamation by other
SCHED_DEADLINE tasks.
5. Tasks CPU affinity
=====================
Deadline tasks cannot have a cpu affinity mask smaller than the root domain they
are created on. So, using ``sched_setaffinity(2)`` won't work. Instead, the
the deadline task should be created in a restricted root domain. This can be
done using the cpuset controller of either cgroup v1 (deprecated) or cgroup v2.
See :ref:`Documentation/admin-guide/cgroup-v1/cpusets.rst <cpusets>` and
:ref:`Documentation/admin-guide/cgroup-v2.rst <cgroup-v2>` for more information.
5.1 Using cgroup v1 cpuset controller
-------------------------------------
An example of a simple configuration (pin a -deadline task to CPU0) follows::
mkdir /dev/cpuset
mount -t cgroup -o cpuset cpuset /dev/cpuset
cd /dev/cpuset
mkdir cpu0
echo 0 > cpu0/cpuset.cpus
echo 0 > cpu0/cpuset.mems
echo 1 > cpuset.cpu_exclusive
echo 0 > cpuset.sched_load_balance
echo 1 > cpu0/cpuset.cpu_exclusive
echo 1 > cpu0/cpuset.mem_exclusive
echo $$ > cpu0/tasks
chrt --sched-runtime 100000 --sched-period 200000 --deadline 0 yes > /dev/null
5.2 Using cgroup v2 cpuset controller
-------------------------------------
Assuming the cgroup v2 root is mounted at ``/sys/fs/cgroup``.
cd /sys/fs/cgroup
echo '+cpuset' > cgroup.subtree_control
mkdir deadline_group
echo 0 > deadline_group/cpuset.cpus
echo 'root' > deadline_group/cpuset.cpus.partition
echo $$ > deadline_group/cgroup.procs
chrt --sched-runtime 100000 --sched-period 200000 --deadline 0 yes > /dev/null
6. Future plans
===============
Still missing:
- programmatic way to retrieve current runtime and absolute deadline
- refinements to deadline inheritance, especially regarding the possibility
of retaining bandwidth isolation among non-interacting tasks. This is
being studied from both theoretical and practical points of view, and
hopefully we should be able to produce some demonstrative code soon;
- (c)group based bandwidth management, and maybe scheduling;
- access control for non-root users (and related security concerns to
address), which is the best way to allow unprivileged use of the mechanisms
and how to prevent non-root users "cheat" the system?
As already discussed, we are planning also to merge this work with the EDF
throttling patches [https://lore.kernel.org/r/[email protected]] but we still are in
the preliminary phases of the merge and we really seek feedback that would
help us decide on the direction it should take.
Appendix A. Test suite
======================
The SCHED_DEADLINE policy can be easily tested using two applications that
are part of a wider Linux Scheduler validation suite. The suite is
available as a GitHub repository: https://github.com/scheduler-tools.
The first testing application is called rt-app and can be used to
start multiple threads with specific parameters. rt-app supports
SCHED_{OTHER,FIFO,RR,DEADLINE} scheduling policies and their related
parameters (e.g., niceness, priority, runtime/deadline/period). rt-app
is a valuable tool, as it can be used to synthetically recreate certain
workloads (maybe mimicking real use-cases) and evaluate how the scheduler
behaves under such workloads. In this way, results are easily reproducible.
rt-app is available at: https://github.com/scheduler-tools/rt-app.
rt-app does not accept command line arguments, and instead reads from a JSON
configuration file. Here is an example ``config.json``:
.. code-block:: json
{
"tasks": {
"dl_task": {
"policy": "SCHED_DEADLINE",
"priority": 0,
"dl-runtime": 10000,
"dl-period": 100000,
"dl-deadline": 100000
},
"fifo_task": {
"policy": "SCHED_FIFO",
"priority": 10,
"runtime": 20000,
"sleep": 130000
}
},
"global": {
"duration": 5
}
}
On running ``rt-app config.json``, it creates 2 threads. The first one,
scheduled by SCHED_DEADLINE, executes for 10ms every 100ms. The second one,
scheduled at SCHED_FIFO priority 10, executes for 20ms every 150ms. The test
will run for a total of 5 seconds.
Please refer to the rt-app documentation for the JSON schema and more examples.
The second testing application is done using chrt which has support
for SCHED_DEADLINE.
The usage is straightforward::
# chrt -d -T 10000000 -D 100000000 0 ./my_cpuhog_app
With this, my_cpuhog_app is put to run inside a SCHED_DEADLINE reservation
of 10ms every 100ms (note that parameters are expressed in nanoseconds).
You can also use chrt to create a reservation for an already running
application, given that you know its pid::
# chrt -d -T 10000000 -D 100000000 -p 0 my_app_pid
Appendix B. Minimal main()
==========================
We provide in what follows a simple (ugly) self-contained code snippet
showing how SCHED_DEADLINE reservations can be created by a real-time
application developer::
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <linux/unistd.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <sys/syscall.h>
#include <pthread.h>
#define gettid() syscall(__NR_gettid)
#define SCHED_DEADLINE 6
/* XXX use the proper syscall numbers */
#ifdef __x86_64__
#define __NR_sched_setattr 314
#define __NR_sched_getattr 315
#endif
#ifdef __i386__
#define __NR_sched_setattr 351
#define __NR_sched_getattr 352
#endif
#ifdef __arm__
#define __NR_sched_setattr 380
#define __NR_sched_getattr 381
#endif
static volatile int done;
struct sched_attr {
__u32 size;
__u32 sched_policy;
__u64 sched_flags;
/* SCHED_NORMAL, SCHED_BATCH */
__s32 sched_nice;
/* SCHED_FIFO, SCHED_RR */
__u32 sched_priority;
/* SCHED_DEADLINE (nsec) */
__u64 sched_runtime;
__u64 sched_deadline;
__u64 sched_period;
};
int sched_setattr(pid_t pid,
const struct sched_attr *attr,
unsigned int flags)
{
return syscall(__NR_sched_setattr, pid, attr, flags);
}
int sched_getattr(pid_t pid,
struct sched_attr *attr,
unsigned int size,
unsigned int flags)
{
return syscall(__NR_sched_getattr, pid, attr, size, flags);
}
void *run_deadline(void *data)
{
struct sched_attr attr;
int x = 0;
int ret;
unsigned int flags = 0;
printf("deadline thread started [%ld]\n", gettid());
attr.size = sizeof(attr);
attr.sched_flags = 0;
attr.sched_nice = 0;
attr.sched_priority = 0;
/* This creates a 10ms/30ms reservation */
attr.sched_policy = SCHED_DEADLINE;
attr.sched_runtime = 10 * 1000 * 1000;
attr.sched_period = attr.sched_deadline = 30 * 1000 * 1000;
ret = sched_setattr(0, &attr, flags);
if (ret < 0) {
done = 0;
perror("sched_setattr");
exit(-1);
}
while (!done) {
x++;
}
printf("deadline thread dies [%ld]\n", gettid());
return NULL;
}
int main (int argc, char **argv)
{
pthread_t thread;
printf("main thread [%ld]\n", gettid());
pthread_create(&thread, NULL, run_deadline, NULL);
sleep(10);
done = 1;
pthread_join(thread, NULL);
printf("main dies [%ld]\n", gettid());
return 0;
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
주의 사항과 SCHED_DEADLINE 개요
1-46SCHED_DEADLINE 및 RT bandwidth 설정을 잘못 바꾸면 system 동작이 예측 불가능해지거나 불안정해질 수 있다. 이 interface를 조정하는 root 사용자는 task model과 admission control을 이해해야 한다.
sched_dl scheduling class의 SCHED_DEADLINE policy는 Earliest Deadline First(EDF)를 기본 선택 알고리즘으로 사용하고 Constant Bandwidth Server(CBS)를 결합한다. EDF는 가장 이른 scheduling deadline을 가진 task를 고르고, CBS는 각 task가 예약한 bandwidth를 넘지 못하게 해 task 사이 간섭을 격리한다.
CBS와 EDF의 기본 동작
47-125SCHED_DEADLINE task는 runtime, deadline, period 세 parameter를 가진다. 각 period마다 runtime만큼 CPU 실행 시간을 받고, 그 runtime은 period 시작에서 deadline 이내에 제공되어야 한다. task가 wakeup할 때 CBS가 이 보장과 일치하는 scheduling deadline을 계산하고, EDF가 가장 이른 scheduling deadline의 task를 실행한다.
admission control이 system overload를 막아야만 runtime을 deadline 안에 제공한다는 보장이 성립한다. CBS는 각 period에 task가 runtime보다 오래 실행하지 못하게 하여 bandwidth isolation을 제공하고, 전통적인 엄격한 periodic task model을 따르지 않는 workload도 reservation 안에 격리한다.
| 값 | 의미 |
|---|---|
| runtime | 각 period에 예약된 최대 실행 시간 |
| deadline | period 시작 뒤 runtime 제공을 완료해야 하는 상대 시간 |
| period | reservation이 반복되는 간격 |
| scheduling deadline | CBS가 EDF 선택에 사용하는 현재 절대 deadline |
| remaining runtime | 현재 reservation에서 남은 실행 budget |
wakeup 시 scheduler는 아래 비율을 비교한다. scheduling deadline이 이미 현재 시각보다 작거나, 남은 시간에 비해 remaining runtime의 밀도가 원래 reservation bandwidth보다 크면 새로운 server instance로 재설정한다.
remaining_runtime / (scheduling_deadline - current_time) > runtime / period
scheduling_deadline = current_time + deadline
remaining_runtime = runtime
조건이 거짓이면 이전 scheduling deadline과 remaining runtime을 유지한다. task가 t만큼 실행할 때 remaining_runtime -= t로 budget을 차감하며, 실제 accounting은 tick 또는 deschedule/preemption 지점에서 수행한다.
remaining runtime이 0 이하가 되면 task는 throttled 또는 depleted 상태가 되어 현재 scheduling deadline까지 실행할 수 없다. 그 deadline을 replenishment time으로 삼고 시각이 도달하면 scheduling_deadline += period, remaining_runtime += runtime으로 다음 budget을 공급한다.
sched_attr.sched_flags에 SCHED_FLAG_DL_OVERRUN을 지정하면 task가 runtime을 초과했을 때 SIGXCPU signal을 받을 수 있다.
wakeup에서 budget을 검사하고 실행·throttle·replenishment로 이어지는 상태 흐름이다.
GRUB bandwidth reclaim
126-289SCHED_FLAG_RECLAIM을 켜면 deadline task의 사용하지 않은 bandwidth를 GRUB(Greedy Reclamation of Unused Bandwidth) 알고리즘으로 회수한다.
| 상태 | 조건 |
|---|---|
| ActiveContending | 실행 중이거나 runqueue에서 실행 가능 |
| ActiveNonContending | 방금 block했지만 아직 0-lag time을 지나지 않음 |
| Inactive | block 상태이며 0-lag time을 지남 |
task가 block하자마자 inactive가 되면 아직 보장에 필요한 bandwidth까지 다른 task가 가져가 real-time guarantee를 깨뜨릴 수 있다. 그래서 먼저 ActiveNonContending으로 이동하고 0-lag time에 inactive timer를 설정한다.
zero_lag_time = deadline - (remaining_runtime * dl_period) / dl_runtime
timer 전에 wakeup하면 timer를 취소하고 ActiveContending으로 돌아간다. 다른 runqueue에서 깨면 이전 runqueue의 active utilization에서 제거하고 새 runqueue에 더한다. 다른 CPU의 inactive timer와 wakeup race를 조정하기 위해 dl_non_contending flag가 runqueue 밖이지만 아직 active bandwidth에 포함된 상태를 표시한다.
inactive timer가 실행되면 Inactive가 되고 runqueue의 active utilization에서 제거된다. Inactive task가 다시 wakeup하면 새 runqueue의 active utilization에 bandwidth를 더하고 ActiveContending이 된다.
block 직후에는 예약 보장을 위해 bandwidth를 바로 회수하지 않고 0-lag까지 유예한다.
| runqueue 값 | 정의 |
|---|---|
| running_bw | ActiveContending과 ActiveNonContending task bandwidth의 합 |
| this_bw | Inactive를 포함해 해당 runqueue에 속한 모든 task bandwidth의 합 |
| max_bw | deadline task가 쓸 수 있는 최대 bandwidth이며 현재 RT capacity로 설정 |
Inactive task의 bandwidth는 실행 task Ti의 budget 차감 속도를 늦추는 방식으로 회수한다. Ui는 Ti bandwidth, Umax는 RT throttling limit을 반영한 최대 회수 utilization, Uinact=this_bw-running_bw, Uextra는 추가 회수 가능한 runqueue utilization이다.
dq = -(max{Ui, Umax - Uinact - Uextra} / Umax) * dt
runtime=4, period=8인 bandwidth 0.5 task T1과 T2를 생각한다. t=0에는 둘 다 ActiveContending이고 T1이 실행하며 inactive bandwidth가 없어 dq=-1*dt다. t=2에 T1이 remaining runtime 2를 남기고 block하면 0-lag는 t=4이고 T2가 정상 속도로 budget을 쓴다.
t=4까지 T1이 깨지 않으면 Inactive가 되어 running_bw가 1에서 0.5로 감소한다. T2는 dq=-0.5*dt로 budget이 절반 속도로 차감되어 T1의 미사용 bandwidth를 회수한다. t=8에 T1이 wakeup하면 ActiveContending으로 돌아가 running_bw가 증가한다.
T1이 t=2에 block하고 t=4에 inactive가 되면 T2의 budget 차감 속도가 절반으로 감소한다.
GRUB-PA energy-aware scheduling
290-301cpufreq의 schedutil governor를 선택하면 SCHED_DEADLINE은 deadline을 만족할 수 있는 최소 CPU frequency로 낮추는 GRUB-PA를 사용한다. 이 동작은 현재 ARM architecture에서만 구현되어 있다.
frequency 전환 시간이 reservation period와 비슷한 크기라면 governor의 반응 지연이 timing guarantee를 해칠 수 있다. 이런 조건에서는 고정 CPU frequency가 deadline miss를 더 줄일 수 있다.
real-time task model과 utilization
303-371SCHED_DEADLINE은 특정 task 종류에 제한되지 않지만, timing guarantee가 필요한 periodic 또는 sporadic multimedia, streaming, control workload에 특히 적합하다.
real-time task는 반복되는 computation phase, 즉 job으로 구성된다. j번째 job J_j는 arrival time r_j, 완료에 필요한 computation time c_j, 완료되어야 하는 absolute deadline d_j를 가진다. max{c_j}가 task의 Worst Case Execution Time(WCET)다.
periodic task는 r_(j+1)=r_j+P이고, sporadic task는 최소 inter-arrival P를 가져 r_(j+1)>=r_j+P다. relative deadline D에 대해 d_j=r_j+D다. 따라서 task model은 Task=(WCET,D,P)로 표현한다.
Task = (WCET, D, P)
U_i = WCET_i / P_i
U_total = sum(WCET_i / P_i)
utilization은 task가 요구하는 CPU 시간 비율이다. CPU가 M개일 때 total utilization이 M보다 크면 모든 deadline을 지킬 수 없고 non-real-time task가 굶을 위험도 있다. U<M이면 non-real-time starvation을 막고 global EDF task tardiness에 상한을 둘 수 있다.
tardiness는 max(0, job finish time - absolute deadline)이다. 원문은 global EDF에서 task별 최대 tardiness가 WCET_max, WCET_min, U_max와 CPU 수 M으로 제한되는 이론식을 제시하며, 핵심은 U<=M이면 response time이 무한히 늘어나지 않는다는 점이다.
단일 CPU schedulability
372-416M=1이거나 각 real-time task를 CPU 하나에 고정하는 partitioned scheduling에서는 deadline 충족 여부를 형식적으로 검사할 수 있다. 모든 task에서 D_i=P_i이면 해당 CPU의 utilization 합이 1 이하일 때에만 EDF가 모든 deadline을 만족한다.
D_i와 P_i가 다른 task가 있으면 density를 WCET_i/min(D_i,P_i)로 정의한다. density 합이 1 이하라는 조건은 schedulable을 보장하는 충분조건이지만 필요조건은 아니다.
sum(WCET_i / min{D_i, P_i}) <= 1
Task_1=(50ms,50ms,100ms), Task_2=(10ms,100ms,100ms)의 density 합은 1.1이지만 EDF는 Task_1을 release 즉시 50ms 실행하고 이어 Task_2를 10ms 실행해 response time 60ms로 두 deadline을 지킨다.
50/min(50,100) + 10/min(100,100) = 1 + 0.1 = 1.1
D_i!=P_i에서 필요충분 검사는 interval t마다 deadline을 지키는 데 필요한 총 processor demand h(t)를 계산해 h(t)<=t인지 확인한다. 모든 t를 검사할 수 없지만 이론적으로 유한 상한 L까지 확인하면 된다. 이 계산은 online admission에 너무 복잡하고 오래 걸려 Linux는 utilization 기반 검사를 사용한다.
다중 CPU global EDF 분석
417-466partition하지 않은 M-CPU global EDF에서는 단순히 utilization이나 density를 고정 상수와 비교하는 것으로 완전한 schedulability를 판정할 수 없다. 모든 D_i=P_i이고 total utilization이 1보다 아주 조금 큰 task set도 CPU 수와 무관하게 deadline을 놓칠 수 있다.
Dhall effect 예에서 Task_1=(P,P,P)이고 나머지 M개 task는 (e,P-1,P-1)이다. 모두 시각 t에 release되면 짧은 deadline t+P-1을 가진 M개 task가 먼저 모든 CPU에서 실행된다. Task_1은 t+e에 시작해 t+e+P에 끝나므로 deadline t+P를 놓친다. e를 0에 가깝게 만들면 total utilization은 1에 임의로 가까워진다.
모든 D_i=P_i일 때 알려진 충분조건 가운데 하나는 아래 식이다. U_max=1이면 오른쪽이 1이 되어 Dhall effect를 반영한다.
sum(WCET_i / P_i) <= M - (M - 1) * U_max
U_max = max(WCET_i / P_i)
따라서 U<M은 global EDF가 모든 deadline을 지킨다는 보장은 아니다. 다만 non-real-time task starvation을 막고 real-time task tardiness와 response time에 유한 상한을 제공한다.
task model과 SCHED_DEADLINE parameter 대응
467-550real-time task의 absolute deadline d_j=r_j+D와 CBS가 EDF 선택에 사용하는 scheduling deadline은 구별해야 한다. admission control이 scheduling deadline 충족을 보장할 때 두 parameter 집합을 올바르게 대응시키면 job의 실제 absolute deadline도 보장할 수 있다.
runtime >= WCET
deadline = D
period <= P
runtime이 worst-case 실행 시간을 덮고 reservation period가 실제 최소 inter-arrival보다 길지 않으면 scheduling deadline과 job absolute deadline을 일치시킬 수 있다. runtime>deadline인 요청은 주어진 시간 안에 budget 자체를 실행할 수 없어 admission control이 거부한다.
원문 참고문헌은 Liu와 Layland의 EDF 기반 논문, Abeni와 Buttazzo의 CBS, 단일 CPU processor demand, Dhall effect와 global EDF, tardiness bound, GRUB·multicore reclaim, Linux SCHED_DEADLINE 구현과 GRUB-PA 논문을 포함한다.
admission control과 system-wide bandwidth
552-612runtime을 deadline 안에 보장하려면 사용 가능한 CPU fraction을 task에 배분하는 admission control이 필요하다. deadline task에서는 runtime/period가 전통적인 real-time task utilization에 대응하며 이를 bandwidth라고 부른다.
system-wide 제어는 RT group scheduling의 RT-throttling과 비슷한 procfs knob를 공유한다. deadline task의 per-cgroup bandwidth interface는 아직 정의되지 않았다. SCHED_DEADLINE task 자체가 CBS bandwidth를 가지므로 RT task처럼 상위 계층의 주기적 throttling으로 강제할 필요는 없고, knob는 sched_setattr() admission 시 total utilization cap으로만 쓰인다.
CONFIG_RT_GROUP_SCHED에서는 deadline runtime이 root RT runtime에 accounting되고, 설정이 없으면 같은 knob가 deadline admission에만 쓰인다. M CPU root_domain에서 허용되는 reservation bandwidth 합은 다음 값보다 작아야 한다.
M * (sched_rt_runtime_us / sched_rt_period_us)
sum(runtime_i / period_i) < global_dl_utilization_cap
/proc/sys/kernel/sched_rt_runtime_us에 -1을 쓰면 bandwidth management를 끄고 임의 수준으로 oversubscribe할 수 있지만 timing guarantee도 잃는다.
task interface, 기본값과 sched_yield
613-670periodic/sporadic task를 표현하려면 최대 또는 대표 instance 실행 시간, 연속 instance 사이 최소 간격, 각 instance 완료 시간 제약이 필요하다. Linux는 이를 담는 struct sched_attr와 sched_setattr()/sched_getattr() syscall을 제공한다.
debugging에서는 /proc/<pid>/sched의 dl.runtime과 dl.deadline에서 남은 runtime과 현재 absolute scheduling deadline을 ns 단위로 읽을 수 있다. production code가 이를 programmatically 읽는 정식 interface는 논의 중이다.
기본 sched_rt_runtime_us=950000, sched_rt_period_us=1000000이므로 각 root_domain에서 CPU 수의 95%까지 deadline bandwidth로 예약할 수 있다. non-deadline task에는 최소 5%를 남긴다. deadline=period이고 cpuset으로 partitioned scheduling을 구성하면 이 cap 아래에서 각 task가 period 안에 runtime을 받는 것을 결정적으로 보장할 수 있다.
admission accounting을 깨뜨리지 않도록 SCHED_DEADLINE task는 fork할 수 없다. sched_yield()를 호출하면 현재 remaining runtime 전체를 포기하고 즉시 throttle되어 다음 period의 replenishment까지 기다린다. dl_yielded flag가 이 경로를 표시한다.
이 semantics는 task가 다음 period 시작에 맞춰 다시 wakeup하게 한다. bandwidth reclaim과 결합하면 포기한 남은 budget을 다른 deadline task가 회수할 수 있다.
CPU affinity와 cpuset root domain
672-712deadline task의 CPU affinity mask는 task가 생성된 root domain보다 작을 수 없으므로 sched_setaffinity()로 단순 pinning할 수 없다. 먼저 cgroup cpuset controller로 제한된 root domain을 만들고 그 안에서 task를 SCHED_DEADLINE으로 전환해야 한다.
deprecated cgroup v1에서 CPU0 전용 cpuset을 만드는 원문 예는 다음과 같다.
mkdir /dev/cpuset
mount -t cgroup -o cpuset cpuset /dev/cpuset
cd /dev/cpuset
mkdir cpu0
echo 0 > cpu0/cpuset.cpus
echo 0 > cpu0/cpuset.mems
echo 1 > cpuset.cpu_exclusive
echo 0 > cpuset.sched_load_balance
echo 1 > cpu0/cpuset.cpu_exclusive
echo 1 > cpu0/cpuset.mem_exclusive
echo $$ > cpu0/tasks
chrt --sched-runtime 100000 --sched-period 200000 --deadline 0 yes > /dev/null
cgroup v2 root가 /sys/fs/cgroup에 mount된 경우에는 cpuset controller를 subtree에 활성화하고 CPU0 partition root를 만든다.
cd /sys/fs/cgroup
echo '+cpuset' > cgroup.subtree_control
mkdir deadline_group
echo 0 > deadline_group/cpuset.cpus
echo 'root' > deadline_group/cpuset.cpus.partition
echo $$ > deadline_group/cgroup.procs
chrt --sched-runtime 100000 --sched-period 200000 --deadline 0 yes > /dev/null
남은 과제와 test suite
713-795남은 과제에는 현재 runtime과 absolute deadline을 읽는 programmatic API, 비상호작용 task의 bandwidth isolation을 유지하는 deadline inheritance 개선, cgroup 기반 bandwidth management와 scheduling, non-root access control과 악용 방지가 있다.
scheduler-tools의 rt-app은 SCHED_OTHER, FIFO, RR, DEADLINE thread를 JSON으로 구성해 재현 가능한 synthetic workload를 만든다. 아래 예는 10ms/100ms deadline task와 20ms 실행 후 130ms sleep하는 FIFO priority 10 task를 5초 실행한다.
{
"tasks": {
"dl_task": {
"policy": "SCHED_DEADLINE",
"priority": 0,
"dl-runtime": 10000,
"dl-period": 100000,
"dl-deadline": 100000
},
"fifo_task": {
"policy": "SCHED_FIFO",
"priority": 10,
"runtime": 20000,
"sleep": 130000
}
},
"global": { "duration": 5 }
}
chrt도 SCHED_DEADLINE을 지원한다. -T runtime과 -D deadline은 nanosecond다. 첫 명령은 새 process를 10ms/100ms reservation으로 실행하고, 두 번째는 기존 PID에 같은 reservation을 적용한다.
# chrt -d -T 10000000 -D 100000000 0 ./my_cpuhog_app
# chrt -d -T 10000000 -D 100000000 -p 0 my_app_pid
sched_setattr 최소 실행 예제
796-919원문 예제는 sched_attr를 직접 채워 현재 thread에 10ms/30ms reservation을 설정한다. syscall 번호를 source에 직접 고정한 부분은 demonstration용이며 실제 application은 대상 system의 올바른 UAPI와 libc 지원을 사용해야 한다.
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <linux/unistd.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <sys/syscall.h>
#include <pthread.h>
#define gettid() syscall(__NR_gettid)
#define SCHED_DEADLINE 6
/* XXX use the proper syscall numbers */
#ifdef __x86_64__
#define __NR_sched_setattr 314
#define __NR_sched_getattr 315
#endif
#ifdef __i386__
#define __NR_sched_setattr 351
#define __NR_sched_getattr 352
#endif
#ifdef __arm__
#define __NR_sched_setattr 380
#define __NR_sched_getattr 381
#endif
static volatile int done;
struct sched_attr {
__u32 size;
__u32 sched_policy;
__u64 sched_flags;
__s32 sched_nice;
__u32 sched_priority;
__u64 sched_runtime;
__u64 sched_deadline;
__u64 sched_period;
};
int sched_setattr(pid_t pid, const struct sched_attr *attr,
unsigned int flags)
{
return syscall(__NR_sched_setattr, pid, attr, flags);
}
int sched_getattr(pid_t pid, struct sched_attr *attr,
unsigned int size, unsigned int flags)
{
return syscall(__NR_sched_getattr, pid, attr, size, flags);
}
void *run_deadline(void *data)
{
struct sched_attr attr;
int x = 0;
int ret;
unsigned int flags = 0;
printf("deadline thread started [%ld]\n", gettid());
attr.size = sizeof(attr);
attr.sched_flags = 0;
attr.sched_nice = 0;
attr.sched_priority = 0;
attr.sched_policy = SCHED_DEADLINE;
attr.sched_runtime = 10 * 1000 * 1000;
attr.sched_period = attr.sched_deadline = 30 * 1000 * 1000;
ret = sched_setattr(0, &attr, flags);
if (ret < 0) {
done = 0;
perror("sched_setattr");
exit(-1);
}
while (!done)
x++;
printf("deadline thread dies [%ld]\n", gettid());
return NULL;
}
int main(int argc, char **argv)
{
pthread_t thread;
printf("main thread [%ld]\n", gettid());
pthread_create(&thread, NULL, run_deadline, NULL);
sleep(10);
done = 1;
pthread_join(thread, NULL);
printf("main dies [%ld]\n", gettid());
return 0;
}
worker thread는 sched_setattr(0, ...)로 자신을 SCHED_DEADLINE으로 바꾸고 10초 동안 busy loop한다. runtime 10ms를 소진할 때마다 CBS가 throttle하고 30ms period마다 replenish한다. main thread는 done을 설정하고 join한다.
설정 전에 알아야 할 점
sched-deadline.rst:1-46SCHED_DEADLINE은 일반 우선순위 조정 기능이 아니라 CPU 시간을 예약하는 실시간 스케줄링 클래스입니다. runtime, deadline, period를 잘못 조합하거나 시스템 전체 대역폭 한계를 과도하게 높이면 일반 태스크가 실행할 시간을 잃거나 실시간 보장이 무너질 수 있습니다.
구현은 두 역할로 나뉩니다. EDF는 절대 deadline이 가장 이른 runnable task를 고르고, CBS는 각 task가 period마다 runtime 이상을 소비하지 못하게 막습니다. EDF만 사용하면 과도하게 실행한 task가 다른 예약을 침범하지만 CBS를 함께 사용하면 task별 CPU 대역폭을 격리할 수 있습니다.