[커널 18차] 84주차
2022.12.31 22:42
cfs pelt 진행중.
git : https://github.com/iamroot18/5.10/commit/58990053cb5e7f0f733d7d88d013d1db3a0284eb
diff --git a/include/linux/sched.h b/include/linux/sched.h
index c68b7e8a552b..80f60bbf021b 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -447,6 +447,36 @@ struct load_weight {
* time. Since max value of util_est.enqueued for a task is 1024 (PELT util_avg
* for a task) it is safe to use MSB.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * struct util_est - FAIR 작업의 추정 활용
+ * @enqueued: 작업/CPU의 즉각적인 예상 사용률
+ * @ewma: 작업의 지수 가중 이동 평균(EWMA) 사용률입니다.
+ *
+ * FAIR 작업 활용도의 지수 가중 이동 평균(EWMA)을 추적하는 데이터 구조를 지원합니다.
+ * 작업이 활성화를 완료할 때마다 새 샘플이 이동 평균에 추가됩니다. 샘플의 가중치는
+ * EWMA가 작업 부하의 일시적인 변화에 상대적으로 덜 민감하도록 선택됩니다.
+ *
+ * 대기열에 추가된 속성은 작업 및 cpus에 대해 약간 다른 의미를 갖습니다.
+ * - task: 마지막 작업 대기열에서 빼기 시간에 작업의 util_avg.
+ * - cfs_rq: 해당 CPU의 각 RUNNABLE 작업에 대한 util_est.enqueued의 합계 따라서 작업의
+ * util_est.enqueued는 해당 작업이 현재 대기열에 있는 CPU의 예상 사용률에 대한 기여도를
+ * 나타냅니다.
+ *
+ * 작업에 대해서만 과거 순간 예상 사용률의 이동 평균을 추적합니다. 이를 통해 거의
+ * 주기적인 작업을 사용할 때 산발적인 감소를 흡수할 수 있습니다.
+ *
+ * UTIL_AVG_UNCHANGED 플래그는 util_est를 util_avg 업데이트와 동기화하는 데 사용됩니다.
+ * 작업이 대기열에서 제거되면 그 동안 util_avg가 업데이트되지 않은 경우 해당 util_est를
+ * 업데이트하면 안 됩니다.
+ *
+ * 이 정보는 큐에서 제거할 때 util_est.enqueued의 MSB 비트에 매핑됩니다. 작업에 대한
+ * util_est.enqueued의 최대 값은 1024(작업에 대한 PELT util_avg)이므로 MSB를 사용하는
+ * 것이 안전합니다.
+ *
+ * - enqueued의 MSB는 UTIL_AVG_UNCHANGED로 사용될수있다.
+ */
struct util_est {
unsigned int enqueued;
unsigned int ewma;
@@ -546,10 +576,19 @@ struct util_est {
* 그런 다음 오버플로 문제를 고려하는 것은 load_weight의 책임입니다.
*/
struct sched_avg {
+/*
+ * IAMROOT, 2022.12.31:
+ * - ns단위 갱신될때 1024 미만 단위는 버려진다.(___update_load_sum()) 참고
+ */
u64 last_update_time;
u64 load_sum;
u64 runnable_sum;
u32 util_sum;
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - accumulate_sum()에서, 직전 주기에서 1ms가 못되서 남겨둔 나머지 시간.
+ */
u32 period_contrib;
unsigned long load_avg;
unsigned long runnable_avg;
@@ -617,7 +656,8 @@ struct sched_entity {
struct list_head group_node;
/*
* IAMROOT, 2022.12.17:
- * - sched_entity 가 rq에 들어가 있는 경우 1
+ * - sched_entity 가 rq에 들어가 있는 경우 1. 시간할당을 받는 상태
+ * 그게 아니면 sleep인 상태.
*/
unsigned int on_rq;
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8871f8563c8f..bb9c75046af8 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -3226,6 +3226,15 @@ account_entity_dequeue(struct cfs_rq *cfs_rq, struct sched_entity *se)
* memory. This allows lockless observations without ever seeing the negative
* values.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * ---
+ * *ptr -= _val;
+ * if (*ptr < 0)
+ * *ptr = 0;
+ * ---
+ * - 뺄섬 결과값이 음수면 0으로 한다.
+ */
#define sub_positive(_ptr, _val) do { \
typeof(_ptr) ptr = (_ptr); \
typeof(*ptr) val = (_val); \
@@ -3702,9 +3711,82 @@ void set_task_rq_fair(struct sched_entity *se,
*
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * 마이그레이션 중에 sched_entity가 PELT 계층 구조에 합류/탈퇴할 때 기여도를
+ * 전파해야 합니다. 이 전파의 핵심은 각 그룹에 대한 불변성입니다.
+ *
+ * ge->avg == grq->avg (1)
+ *
+ * IFF_ 순수한 실행 및 실행 가능한 합계를 봅니다. 계층 구조의 다른 지점에서
+ * 매우 동일한 엔터티를 나타내기 때문입니다.
+ *
+ * 위의 update_tg_cfs_util() 및 update_tg_cfs_runnable()은 사소하고 단순히
+ * 실행/실행 가능한 합계를 복사합니다(그러나 그룹 엔티티 및 그룹 rq에 PELT
+ * 창이 정렬되어 있지 않기 때문에 여전히 잘못됨).
+ *
+ * 그러나 update_tg_cfs_load()는 더 복잡합니다. 그래서 우리는:
+ *
+ * ge->avg.load_avg = ge->load.weight * ge->avg.runnable_avg (2)
+ *
+ * 그리고 util과 마찬가지로 실행 가능한 부분을 직접 양도할 수 있어야 하므로
+ * 다음과 같이 간단하게 접근할 수 있습니다.
+ *
+ * grq->avg.load_avg = grq->load.weight * grq->avg.runnable_avg (3)
+ *
+ * 그리고 (1)에 따라 다음이 있습니다.
+ *
+ * ge->avg.runnable_avg == grq->avg.runnable_avg
+ *
+ * 다음을 제공합니다.
+ *
+ * ge->load.weight * grq->avg.load_avg
+ * ge->avg.load_avg = ----------------------------------- (4)
+ * grq->load.weight
+ *
+ * 틀린 것만 빼면!
+ *
+ * 엔터티의 경우 historical 가중치는 중요하지 않고 실제로는 미래에만 관심이
+ * 있으므로 순수 실행 가능한 합계를 고려할 수 있지만 실행 대기열은 이를
+ * 수행할 수 없습니다.
+ *
+ * 우리는 특히 실행 대기열이 과거 가중치를 포함하는 load_avg를 갖기를 원합니다.
+ * 그것들은 차단된 로드, 즉 (곧) 우리에게 돌아올 것으로 예상되는 로드를 나타냅니다.
+ * 이것은 가중치를 합계의 필수적인 부분으로 유지해야만 작동합니다. 따라서
+ * (3)에 따라 분해할 수 없습니다.
+ *
+ * 이것이 작동하지 않는 또 다른 이유는 runnable이 0-sum 엔터티가 아니기 때문입니다.
+ * 각각 2/3 시간 동안 실행 가능한 2개의 작업이 있는 rq를 상상해 보십시오.
+ * 그런 다음 rq 자체는 이러한 작업의 실행 가능한 섹션이 겹치는 방식
+ * (또는 겹치지 않음)에 따라 2/3에서 1 사이 어디에서나 실행 가능합니다.
+ * rq를 전체적으로 완벽하게 정렬하려면 시간의 2/3를 실행할 수 있습니다.
+ * 그러나 항상 최소한 하나의 실행 가능한 작업이 있는 경우 전체 rq는 항상
+ * 실행 가능합니다.
+ *
+ * 그래서 우리는 근사해야 할 것입니다.. :/
+ *
+ * 주어진 제약조건:
+ *
+ * ge->avg.running_sum <= ge->avg.runnable_sum <= LOAD_AVG_MAX
+ *
+ * 최소한의 중첩을 가정하여 rq에 runnable을 추가하는 규칙을 구성할 수 있습니다.
+ *
+ * 제거할 때 각 작업이 동등하게 실행 가능하다고 가정합니다. 결과는 다음과 같습니다.
+ *
+ * grq->avg.runnable_sum = grq->avg.load_sum / grq->load.weight
+ *
+ * XXX:
+ * runnable > running ? 부분에 대해서만 이 작업을 수행합니다.
+ */
static inline void
update_tg_cfs_util(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq)
{
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - @gcfs_rq나 @se에 변화가 생긴경우에만 고려한다.
+ */
long delta = gcfs_rq->avg.util_avg - se->avg.util_avg;
u32 divider;
@@ -3719,14 +3801,26 @@ update_tg_cfs_util(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq
divider = get_pelt_divider(&cfs_rq->avg);
/* Set new sched_entity's utilization */
+/*
+ * IAMROOT, 2022.12.31:
+ * - gcfs_rq값을 new로 생각하고 group se로 update한다.
+ */
se->avg.util_avg = gcfs_rq->avg.util_avg;
se->avg.util_sum = se->avg.util_avg * divider;
/* Update parent cfs_rq utilization */
+/*
+ * IAMROOT, 2022.12.31:
+ * - 소속 @cfs_rq엔 변화된값을 적용한다.
+ */
add_positive(&cfs_rq->avg.util_avg, delta);
cfs_rq->avg.util_sum = cfs_rq->avg.util_avg * divider;
}
+/*
+ * IAMROOT, 2022.12.31:
+ * - util과 동일하게 진행한다.
+ */
static inline void
update_tg_cfs_runnable(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq)
{
@@ -3752,6 +3846,10 @@ update_tg_cfs_runnable(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cf
cfs_rq->avg.runnable_sum = cfs_rq->avg.runnable_avg * divider;
}
+/*
+ * IAMROOT, 2022.12.31:
+ * -
+ */
static inline void
update_tg_cfs_load(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq)
{
@@ -3771,24 +3869,52 @@ update_tg_cfs_load(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq
*/
divider = get_pelt_divider(&cfs_rq->avg);
+/*
+ * IAMROOT, 2022.12.31:
+ * - 증가됬다면
+ */
if (runnable_sum >= 0) {
/*
* Add runnable; clip at LOAD_AVG_MAX. Reflects that until
* the CPU is saturated running == runnable.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - 기존값을 합산한다.
+ */
runnable_sum += se->avg.load_sum;
+/*
+ * IAMROOT, 2022.12.31:
+ * - 예외처리. divider보다 초과하면 말이안된다.
+ */
runnable_sum = min_t(long, runnable_sum, divider);
} else {
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - 감소됬다면.
+ */
/*
* Estimate the new unweighted runnable_sum of the gcfs_rq by
* assuming all tasks are equally runnable.
*/
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - 반영을 할려는 cfs_rq의 weight와 gcfs_rq의 weight가 다를수도잇는 상태.
+ * gcfs_rq의 load_sum에서 weight가중치를 제거한다.
+ */
if (scale_load_down(gcfs_rq->load.weight)) {
load_sum = div_s64(gcfs_rq->avg.load_sum,
scale_load_down(gcfs_rq->load.weight));
}
/* But make sure to not inflate se's runnable */
+/*
+ * IAMROOT, 2022.12.31:
+ * - 일반적으로 cfs_rq == se지만 변경이 일어난 상태.
+ * runnable_sum이 감소로 들어왔으므로 min비교를 한다.
+ */
runnable_sum = min(se->avg.load_sum, load_sum);
}
@@ -3798,14 +3924,28 @@ update_tg_cfs_load(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq
* running_sum is in [0 : LOAD_AVG_MAX << SCHED_CAPACITY_SHIFT]
* runnable_sum is in [0 : LOAD_AVG_MAX]
*/
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - runnable_sum이 running_sum보다 더 크거나 같은 개념.
+ * (ge->avg.running_sum <= ge->avg.runnable_sum <= LOAD_AVG_MAX )
+ */
running_sum = se->avg.util_sum >> SCHED_CAPACITY_SHIFT;
runnable_sum = max(runnable_sum, running_sum);
+/*
+ * IAMROOT, 2022.12.31:
+ * - se에 대한 load_avg를 weights가중치를 포함해 계산한다.
+ */
load_sum = (s64)se_weight(se) * runnable_sum;
load_avg = div_s64(load_sum, divider);
se->avg.load_sum = runnable_sum;
+/*
+ * IAMROOT, 2022.12.31:
+ * - avg에 대한 변화가 있으면 소속 cfs_rq에 delta를 가중한다.
+ */
delta = load_avg - se->avg.load_avg;
if (!delta)
return;
@@ -3816,6 +3956,11 @@ update_tg_cfs_load(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq
cfs_rq->avg.load_sum = cfs_rq->avg.load_avg * divider;
}
+/*
+ * IAMROOT, 2022.12.31:
+ * - propagate를 해야된다는걸 알리고, 값을 누적시켜놓는다.
+ * 나중에 할것이다.
+ */
static inline void add_tg_cfs_propagate(struct cfs_rq *cfs_rq, long runnable_sum)
{
cfs_rq->propagate = 1;
@@ -3823,13 +3968,26 @@ static inline void add_tg_cfs_propagate(struct cfs_rq *cfs_rq, long runnable_sum
}
/* Update task and its cfs_rq load average */
+/*
+ * IAMROOT, 2022.12.31:
+ * -
+ */
static inline int propagate_entity_load_avg(struct sched_entity *se)
{
struct cfs_rq *cfs_rq, *gcfs_rq;
+/*
+ * IAMROOT, 2022.12.31:
+ * - entity가 task group인 경우에만 propagate 하면된다.
+ */
if (entity_is_task(se))
return 0;
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - propagate 요청이 있는지 확인한다.
+ */
gcfs_rq = group_cfs_rq(se);
if (!gcfs_rq->propagate)
return 0;
@@ -3838,6 +3996,10 @@ static inline int propagate_entity_load_avg(struct sched_entity *se)
cfs_rq = cfs_rq_of(se);
+/*
+ * IAMROOT, 2022.12.31:
+ * - @se가 소속된 cfs_rq에 propagate를 알린다.
+ */
add_tg_cfs_propagate(cfs_rq, gcfs_rq->prop_runnable_sum);
update_tg_cfs_util(cfs_rq, se, gcfs_rq);
@@ -3909,6 +4071,32 @@ static inline void add_tg_cfs_propagate(struct cfs_rq *cfs_rq, long runnable_sum
* Since both these conditions indicate a changed cfs_rq->avg.load we should
* call update_tg_load_avg() when this function returns true.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * update_cfs_rq_load_avg - cfs_rq의 로드/유틸 평균 업데이트
+ * @now: cfs_rq_clock_pelt()에 따른 현재 시간.
+ * @cfs_rq: 업데이트할 cfs_rq.
+ *
+ * cfs_rq avg는 모든 엔터티(차단 및 실행 가능) avg의 직접 합계입니다.
+ * 즉각적인 결과는 모든 (공정한) 작업이 연결되어야 한다는 것입니다.
+ * post_init_entity_util_avg()를 참조하십시오.
+ *
+ * cfs_rq->avg는 예를 들어 task_h_load() 및 update_cfs_share()에 사용됩니다.
+ *
+ * 로드가 감소했거나 로드를 제거한 경우 true를 반환합니다.
+ *
+ * 이 두 조건 모두 변경된 cfs_rq->avg.load를 나타내므로 이 함수가 true를
+ * 반환할 때 update_tg_load_avg()를 호출해야 합니다.
+ *
+ * @return decay가 됬으면 return 1.
+ *
+ * - 1. removed된게 있으면 removed field를 초기화하면서,
+ * removed됬던 값들을 cfs_rq에 적용한다.
+ * 2. cfs_rq load update.
+ * 3. Propagate 요청 여부 set.
+ * 4. removed가 된게 있거나 cfs_rq load가 됬다면 decay가 된것이므로 return 1.
+ */
static inline int
update_cfs_rq_load_avg(u64 now, struct cfs_rq *cfs_rq)
{
@@ -3916,10 +4104,19 @@ update_cfs_rq_load_avg(u64 now, struct cfs_rq *cfs_rq)
struct sched_avg *sa = &cfs_rq->avg;
int decayed = 0;
+/*
+ * IAMROOT, 2022.12.31:
+ * - removed된게 있었다면.
+ */
if (cfs_rq->removed.nr) {
unsigned long r;
u32 divider = get_pelt_divider(&cfs_rq->avg);
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - 0값들과 swap을 하면서 old는 지역변수에 가져온다.
+ */
raw_spin_lock(&cfs_rq->removed.lock);
swap(cfs_rq->removed.util_avg, removed_util);
swap(cfs_rq->removed.load_avg, removed_load);
@@ -3927,6 +4124,12 @@ update_cfs_rq_load_avg(u64 now, struct cfs_rq *cfs_rq)
cfs_rq->removed.nr = 0;
raw_spin_unlock(&cfs_rq->removed.lock);
+/*
+ * IAMROOT, 2022.12.31:
+ * - removed된 avg값들을 cfs_rq에 avg, sum에 빼준다.
+ * sum / div = avg
+ * sum = avg * div
+ */
r = removed_load;
sub_positive(&sa->load_avg, r);
sa->load_sum = sa->load_avg * divider;
@@ -3943,12 +4146,24 @@ update_cfs_rq_load_avg(u64 now, struct cfs_rq *cfs_rq)
* removed_runnable is the unweighted version of removed_load so we
* can use it to estimate removed_load_sum.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * removed_runnable은 removed_load의 비가중 버전이므로 removed_load_sum을
+ * 추정하는 데 사용할 수 있습니다.
+ *
+ * - 감소된값을 propagate해야된다. 감소된값을 기록한다.
+ */
add_tg_cfs_propagate(cfs_rq,
-(long)(removed_runnable * divider) >> SCHED_CAPACITY_SHIFT);
decayed = 1;
}
+/*
+ * IAMROOT, 2022.12.31:
+ * - cfs_rq의 load update.
+ */
decayed |= __update_load_avg_cfs_rq(now, cfs_rq);
#ifndef CONFIG_64BIT
@@ -4048,7 +4263,8 @@ static void detach_entity_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *s
*/
/*
* IAMROOT, 2022.12.22:
- * - taskgroup을 update하라는 의미.
+ * - UPDATE_TG: taskgroup을 update하라는 의미.
+ * - DO_ATTACH : cfs rq에 enqueue 된경우.
*/
#define UPDATE_TG 0x1
#define SKIP_AGE_LOAD 0x2
@@ -4057,7 +4273,9 @@ static void detach_entity_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *s
/* Update task and its cfs_rq load average */
/*
* IAMROOT, 2022.12.22:
- * - TODO
+ * @flags entity_tick에서 불러졌을경우 : UPDATE_TG
+ * enqueue_entity의 경우 : UPDATE_TG | DO_ATTACH
+ * attach_entity_cfs_rq : ATTACH_AGE_LOAD sched_feat가 없는경우 SKIP_AGE_LOAD
*/
static inline void update_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
@@ -4073,13 +4291,23 @@ static inline void update_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *s
* - google-translate
* 마이그레이션 후 새 CPU로 옮기기 위한 작업 로드 평균 추적 및 마이그레이션에서
* task_h_load calc에 대한 그룹 sched_entity 로드 평균 추적
+ *
+ * - 이전에 update를 했다면, @se에 대한 load sum, avg를 구한다.
*/
if (se->avg.last_update_time && !(flags & SKIP_AGE_LOAD))
__update_load_avg_se(now, cfs_rq, se);
+/*
+ * IAMROOT, 2022.12.31:
+ * - cfs_rq에 대한 removed 처리 및, load update.
+ */
decayed = update_cfs_rq_load_avg(now, cfs_rq);
decayed |= propagate_entity_load_avg(se);
+/*
+ * IAMROOT, 2022.12.31:
+ * - 최초이자(entity가 한번도 계산된적없음), ATTACH flag가 있는경우 진입.)
+ */
if (!se->avg.last_update_time && (flags & DO_ATTACH)) {
/*
@@ -11598,7 +11826,8 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) {}
* - scheduler_tick 에서 호출할 때 매개변수 queued = 0
*
* @rq: running task의 runqueue
- * @queued : task가 아직 처리되지 않은 보류중인 tick이 있는지 있는지.
+ * @queued : @curr가 rq에 있는지의 여부. rq에 있으면 true, 없으면(즉 현재 cpu에서 실행중)
+ * false.
*/
static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
{
diff --git a/kernel/sched/pelt.c b/kernel/sched/pelt.c
index 6b950b342862..9b72bc86cd46 100644
--- a/kernel/sched/pelt.c
+++ b/kernel/sched/pelt.c
@@ -32,10 +32,50 @@
* Approximate:
* val * y^n, where y^32 ~= 0.5 (~1 scheduling period)
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * --- LOAD_AVG_PERIOD.
+ * - 0.9785^n, n은 1부터 무한의 sum값
+ * sum = a/(1-r)
+ * = 0.9785 / (1 - 0.9785)
+ * = 0.9789 / 0.0215
+ * = 45.3777..
+ * 이걸 이진화정수화.
+ * 45.3777 * 1024 = 46467
+ * 약 LOAD_AVG_PERIOD값 비슷하게 나온다.
+ * - LOAD_AVG_PERIOD는 n = 0부터 무한대이므로 1024를 더해준다.
+ * 46467 + 1024 = 47491
+ * 내림한 수까지 고려하며ㅓㄴ LOAD_AVG_PERIOD값이 나올것이다.
+ * ---
+ * - ex) val = 8192
+ * -- n == 1인경우
+ x = mul_u64_u32_shr(8192, runnable_avg_yN_inv[1], 32);
+ = mul_u64_u32_shr(8192, 0xfa83b2da, 32);
+ = (8192 * 0xfa83b2da) >> 32
+ = 8016
+ *
+ * -- n == 65인경우
+ val >>= local_n / LOAD_AVG_PERIOD;
+ = 8192 >> (65 / 32)
+ = 8192 >> 2 //즉 0.5 * 0.5의 개념
+ = 2048
+ local_n %= LOAD_AVG_PERIOD;
+ = 65 % 32;
+ = 1
+ x = mul_u64_u32_shr(2048, runnable_avg_yN_inv[1], 32);
+ = (2048 * 0xfa83b2da) >> 32
+ = 2004
+ * - @val이 n ms의 period로 진입했을때 LOAD_AVG_PERIOD와 runnable_avg_yN_inv table로
+ * 배율만큼 감소킨다.
+ */
static u64 decay_load(u64 val, u64 n)
{
unsigned int local_n;
+/*
+ * IAMROOT, 2022.12.31:
+ * - 너무크면 결국 0에 가깝다.
+ */
if (unlikely(n > LOAD_AVG_PERIOD * 63))
return 0;
@@ -49,15 +89,43 @@ static u64 decay_load(u64 val, u64 n)
*
* To achieve constant time decay_load.
*/
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - table의 범위는 32이고, table 끝은 0.5배라는 개념.
+ * - n이 32번보다 크면 32배단위로 0.5배를 해주고, 나머지에 대해서만
+ * table을 사용한다.
+ */
if (unlikely(local_n >= LOAD_AVG_PERIOD)) {
val >>= local_n / LOAD_AVG_PERIOD;
local_n %= LOAD_AVG_PERIOD;
}
+/*
+ * IAMROOT, 2022.12.31:
+ * - table을 사용해 mult shift를 사용한다.
+ */
val = mul_u64_u32_shr(val, runnable_avg_yN_inv[local_n], 32);
return val;
}
+/*
+ * IAMROOT, 2022.12.31:
+ * d1 d2 d3
+ * ^ ^ ^
+ * | | |
+ * |<->|<----------------->|<--->|
+ * ... |---x---|------| ... |------|-----x (now)
+ *
+ * - periods에 대한 decay를 수행한다.
+ * - 기본적인 개념은 다음과 같다.
+ * 1. d1은 d2에서 사용한 periods로 계산을 한다.
+ * 2. d3은 새로온거니까 decay없이 남겨둔다.
+ *
+ * - 결론적으로는 그 전에 본 주석의 식대로 계산한다.
+ * d1 y^p + 1024 \Sum y^n + d3 y^0 (Step 2)
+ * n=1
+ */
static u32 __accumulate_pelt_segments(u64 periods, u32 d1, u32 d3)
{
u32 c1, c2, c3 = d3; /* y^0 == 1 */
@@ -76,6 +144,31 @@ static u32 __accumulate_pelt_segments(u64 periods, u32 d1, u32 d3)
* = 1024 ( \Sum y^n - \Sum y^n - y^0 )
* n=0 n=p
*/
+
+/*
+ * IAMROOT, 2022.12.31:
+ * d1 d2 d3
+ * ^ ^ ^
+ * | | |
+ * |<->|<----------------->|<--->|
+ * ... |---x---|------| ... |------|-----x (now)
+ *
+ * - LOAD_AVG_MAX - decay_load(LOAD_AVG_MAX, periods)
+ * 0 ~ inf까지 의 시간에서 d2의 periods만큼 뺀시간
+ *
+ * - ex) d1 = 300, d2 = 4096, d3 = 800, periods = 5
+ *
+ * c1 = decay_load(300, 5)
+ * = (300 * 0xe5b906e6) >> 32
+ * = 269
+ * c2 = 47742 - decay_load(47742, 5) - 1024
+ * = 47742 - mul_u64_u32_shr(47742, runnable_avg_yN_inv[5], 32) - 1024
+ * = 47742 - (47742 * 0xe5b906e6) >> 32 - 1024
+ * = 47742 - 42,841 - 1024
+ * = 3877
+ * ret = 269 + 3877 + 800
+ * = 5046
+ */
c2 = LOAD_AVG_MAX - decay_load(LOAD_AVG_MAX, periods) - 1024;
return c1 + c2 + c3;
@@ -102,6 +195,18 @@ static u32 __accumulate_pelt_segments(u64 periods, u32 d1, u32 d3)
* d1 y^p + 1024 \Sum y^n + d3 y^0 (Step 2)
* n=1
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * 합계의 세 부분을 합산하십시오. d1 마지막 (불완전한) 기간의 나머지,
+ * d2 전체 기간의 범위 및 d3 (불완전한) 현재 기간의 나머지.
+ *
+ * @return decay가 됬으면 return periods. 아니면 0.
+ *
+ * - load_sum, runnable_sum, util_sum을 1ms단위의 periods주기 동안으로
+ * decay시킨다.
+ * - periods밖으로 남겨진 값들은 decay없이 적산한다(d3)
+ */
static __always_inline u32
accumulate_sum(u64 delta, struct sched_avg *sa,
unsigned long load, unsigned long runnable, int running)
@@ -109,13 +214,49 @@ accumulate_sum(u64 delta, struct sched_avg *sa,
u32 contrib = (u32)delta; /* p == 0 -> delta < 1024 */
u64 periods;
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - 이전에 남겨진 시간을 고려한다.
+ */
delta += sa->period_contrib;
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - 1ms단위의 주기가 몇번 생기는지 계산한다.
+ */
periods = delta / 1024; /* A period is 1024us (~1ms) */
/*
* Step 1: decay old *_sum if we crossed period boundaries.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - period동안의 load_sum, runnable_sum, util_sum을 decay 시킨다.
+ */
if (periods) {
+/*
+ * IAMROOT, 2022.12.31:
+ *
+ * d1 d2 d3
+ * ^ ^ ^
+ * | | |
+ * |<->|<----------------->|<--->|
+ * ... |---x---|------| ... |------|-----x (now)
+ * ^
+ * step1
+ *
+ * p-1
+ * u' = (u + d1) y^p + 1024 \Sum y^n + d3 y^0
+ * n=1
+ *
+ * = u y^p + (Step 1) <-- 이부분에 대한 계산.
+ *
+ * p-1
+ * d1 y^p + 1024 \Sum y^n + d3 y^0 (Step 2)
+ * n=1
+ * - 지나간 시간 만큼 u를 decay 시킨다.
+ */
sa->load_sum = decay_load(sa->load_sum, periods);
sa->runnable_sum =
decay_load(sa->runnable_sum, periods);
@@ -136,12 +277,49 @@ accumulate_sum(u64 delta, struct sched_avg *sa,
* the below usage of @contrib to disappear entirely,
* so no point in calculating it.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * 이것은 다음에 의존합니다.
+ *
+ * if (!load)
+ * runnable = running = 0;
+ *
+ * ___update_load_sum()의 절; 이로 인해 아래 @contrib 사용이 완전히
+ * 사라지므로 계산할 필요가 없습니다.
+ *
+ * ---
+ * d1 : 1024 - sa->period_contrib
+ * d3 : delta
+ * d1 d2 d3
+ * ^ ^ ^
+ * | | |
+ * |<->|<----------------->|<--->|
+ * ... |---x---|------| ... |------|-----x (now)
+ * <=============================>
+ * <--> step2
+ * ^ sa->period_contrib
+ * --
+ *
+ * - periods에 대한 d1 ~ d3까지의 decay된 값을 구한다.
+ * - d1 = 1024 - sa->period_contrib
+ * d3 = delta
+ */
contrib = __accumulate_pelt_segments(periods,
1024 - sa->period_contrib, delta);
}
}
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - 남겨진 시간을 보관한다.
+ */
sa->period_contrib = delta;
+/*
+ * IAMROOT, 2022.12.31:
+ * - decay했던 periods를 value에 적용하여 적산한다.
+ */
if (load)
sa->load_sum += load * contrib;
if (runnable)
@@ -180,6 +358,57 @@ accumulate_sum(u64 delta, struct sched_avg *sa,
* load_avg = u_0` + y*(u_0 + u_1*y + u_2*y^2 + ... )
* = u_0 + u_1*y + u_2*y^2 + ... [re-labeling u_i --> u_{i+1}]
*/
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * 실행 가능한 평균에 대한 historical 기여도를 기하학적 계열의 계수로 나타낼 수
+ * 있습니다. 이를 위해 실행 가능한 기록을 약 1ms(1024us)의 세그먼트로
+ * 세분화합니다. 현재 기간에 해당하는 p_0을 사용하여 p_N 전에 N-ms 발생한
+ * 세그먼트에 레이블을 지정합니다.
+ *
+ * [<- 1024us ->|<- 1024us ->|<- 1024us ->| ...
+ * p0 p1 p2
+ * (now) (~1ms ago) (~2ms ago)
+ *
+ * u_i는 엔티티가 실행 가능한 p_i의 비율을 나타냅니다.
+ *
+ * 그런 다음 분수 u_i를 계수로 지정하여 다음과 같은 과거 부하 표현을 생성합니다.
+ * u_0 + u_1*y + u_2*y^2 + u_3*y^3 + ...
+ *
+ * 합리적인 일정 기간을 기준으로 y를 선택하고 다음을 수정합니다.
+ * y^32 = 0.5
+ *
+ * 이는 ~32ms 전(u_32) 로드 기여도가 마지막 ms(u_0) 내 로드 기여도의 약 절반
+ * 정도 가중치가 부여됨을 의미합니다.
+ *
+ * 기간이 "롤오버"되고 새 u_0`이 있으면 이전 합계에 다시 y를 곱하면
+ * 업데이트하기에 충분합니다.
+ * load_avg = u_0` + y*(u_0 + u_1*y + u_2*y^2 + ... )
+ * = u_0 + u_1*y + u_2*y^2 + ... [re-labeling u_i --> u_{i+1}]
+ *
+ * - 32ms가 지난시점에서 감소율이 50%가 되게 설계한 수식을 적용.
+ * ---
+ * - y값 대략적인 계산
+ * 32logY = log0.5
+ * logY = log0.5 / 32
+ * Y = 10^(log0.5 / 32)
+ * = 0.9786
+ * ---
+ * @running curr가 지금 동작중인지의 여부.
+ * @runnable run을 할수있는 상태를 의미.
+ *
+ * --- running과 runable의 차이
+ * A B running runable
+ * 20% 30% 50% 50%
+ * 100% 50% 100% 150%
+ *
+ * ---
+ *
+ * @return decay가 수행됬으면 return 1
+ *
+ * - last_update_time을 갱신하고 load, runnable, util을 누적한다.
+ */
static __always_inline int
___update_load_sum(u64 now, struct sched_avg *sa,
unsigned long load, unsigned long runnable, int running)
@@ -191,6 +420,13 @@ ___update_load_sum(u64 now, struct sched_avg *sa,
* This should only happen when time goes backwards, which it
* unfortunately does during sched clock init when we swap over to TSC.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * 이것은 시간이 거꾸로 갈 때만 발생해야 합니다. 불행하게도 TSC로 전환할 때
+ * sched clock init 중에 발생합니다.
+.
+ */
if ((s64)delta < 0) {
sa->last_update_time = now;
return 0;
@@ -200,10 +436,21 @@ ___update_load_sum(u64 now, struct sched_avg *sa,
* Use 1024ns as the unit of measurement since it's a reasonable
* approximation of 1us and fast to compute.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * 1us의 합리적인 근사치이고 계산 속도가 빠르므로 1024ns를 측정 단위로 사용합니다.
+ * - us단위로 계산을 진행한다. 1us이상인 경우에만 처리한다.
+ */
delta >>= 10;
if (!delta)
return 0;
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - delta(us)를 일단 누적시킨다.
+ */
sa->last_update_time += delta << 10;
/*
@@ -217,6 +464,21 @@ ___update_load_sum(u64 now, struct sched_avg *sa,
*
* Also see the comment in accumulate_sum().
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * running은 runnable(가중치)의 하위 집합이므로 runnable이 명확한 경우
+ * running을 설정할 수 없습니다. 그러나 현재 se가 이미 대기열에서
+ * 제거되었지만 cfs_rq->curr이 여전히 그것을 가리키는 코너 케이스가 있습니다.
+ * 이는 가중치가 0이 되지만 sched_entity에 대해 실행되지 않고 cfs_rq가 유휴
+ * 상태가 되면 cfs_rq에 대해서도 실행됨을 의미합니다. 예를 들어, 이것은
+ * update_blocked_averages()를 호출하는 idle_balance() 중에 발생합니다.
+ *
+ * accumulate_sum()의 주석도 참조하십시오.
+ *
+ * - corner case 처리. load == 0 이면 se가 rq에서 이미 제거되었다.
+ * 그 와중에 여기에 들어온 상태. 계산을 안하기 위해 0처리.
+ */
if (!load)
runnable = running = 0;
@@ -227,6 +489,17 @@ ___update_load_sum(u64 now, struct sched_avg *sa,
* Step 1: accumulate *_sum since last_update_time. If we haven't
* crossed period boundaries, finish.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * 이제 우리는 측정 단위 경계를 넘었다는 것을 알고 있습니다. *_avg는
+ * 두 단계로 발생합니다.
+ *
+ * 1 단계: last_update_time 이후 *_sum을 누적합니다. 기간 경계를 넘지
+ * 않았다면 끝내십시오.
+ *
+ * - delta 시간에대해서 load, runnable, util을 누적시킨다.
+ */
if (!accumulate_sum(delta, sa, load, runnable, running))
return 0;
@@ -257,6 +530,34 @@ ___update_load_sum(u64 now, struct sched_avg *sa,
* the period_contrib of cfs_rq when updating the sched_avg of a sched_entity
* if it's more convenient.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * *_avg를 *_sum과 동기화할 때 PELT 세그먼트의 현재 위치를 고려해야 합니다.
+ * 그렇지 않으면 세그먼트의 나머지 부분이 아직 경과되지 않은 유휴 시간으로
+ * 간주되어 범위 [1002..1024]에서 원치 않는 진동이 생성됩니다.
+ *
+ * *_sum의 최대값은 시간 세그먼트의 위치에 따라 다르며 다음과 같습니다.
+ *
+ * LOAD_AVG_MAX*y + sa->period_contrib
+ *
+ * 다음과 같이 단순화할 수 있습니다.
+ *
+ * LOAD_AVG_MAX - 1024 + sa->period_contrib
+ *
+ * LOAD_AVG_MAX*y == LOAD_AVG_MAX-1024이기 때문에
+ *
+ * sched 엔터티가 cfs_rq에서 추가, 업데이트 또는 제거되고 sched_avg를
+ * 업데이트해야 하는 경우에도 동일한 주의를 기울여야 합니다. 스케줄러
+ * 엔티티와 이들이 연결된 cfs rq는 동일한 시계를 사용하기 때문에 시간
+ * 세그먼트에서 동일한 위치를 갖습니다. 이는 더 편리하다면 sched_entity의
+ * sched_avg를 업데이트할 때 cfs_rq의 period_contrib를 사용할 수 있음을
+ * 의미합니다.
+ *
+ * - 기존 sum에 대한 avg를 구한다.
+ * - @load는 일종의 배율 개념으로 작동(1 or weight)
+ * __update_load_avg_se() : se_weight(se)로 들어온다.
+ */
static __always_inline void
___update_load_avg(struct sched_avg *sa, unsigned long load)
{
@@ -309,13 +610,27 @@ int __update_load_avg_blocked_se(u64 now, struct sched_entity *se)
/*
* IAMROOT, 2022.12.22:
- * - TODO
+ * @return 갱신된게 있으면 1. 없으면 0
+ *
+ * - @now시간을 기준으로 @se에 대한 load sum과 load avg를 구한다.
*/
int __update_load_avg_se(u64 now, struct cfs_rq *cfs_rq, struct sched_entity *se)
{
+/*
+ * IAMROOT, 2022.12.31:
+ * - on_rq를 load로 판단한다.
+ * - cfs_rq->curr == se
+ * curr와 일치하면 현재 running중인 task라는것. 그래서 running의 의미가 된다.
+ */
if (___update_load_sum(now, &se->avg, !!se->on_rq, se_runnable(se),
cfs_rq->curr == se)) {
+/*
+ * IAMROOT, 2022.12.31:
+ * - decay(충분한 시간이 흐름)가 됬다면 아래 구문들을 수행한다.
+ * - avg를 구한다.
+ * - util_est을 change가능으로 변경한다.
+ */
___update_load_avg(&se->avg, se_weight(se));
cfs_se_util_change(&se->avg);
trace_pelt_se_tp(se);
@@ -325,6 +640,12 @@ int __update_load_avg_se(u64 now, struct cfs_rq *cfs_rq, struct sched_entity *se
return 0;
}
+/*
+ * IAMROOT, 2022.12.31:
+ * - @now시간을 기준으로 @cfs_rq에 대한 load sum과 load avg를 구한다.
+ * - cfs_rq->load.weight
+ * ex) nice가 1024인 task가 3개있ㅎ으면 3072가 된다.
+ */
int __update_load_avg_cfs_rq(u64 now, struct cfs_rq *cfs_rq)
{
if (___update_load_sum(now, &cfs_rq->avg,
diff --git a/kernel/sched/pelt.h b/kernel/sched/pelt.h
index a2df36195abe..26353384f1c8 100644
--- a/kernel/sched/pelt.h
+++ b/kernel/sched/pelt.h
@@ -37,11 +37,26 @@ update_irq_load_avg(struct rq *rq, u64 running)
}
#endif
+/*
+ * IAMROOT, 2022.12.31:
+ * - avg할 범위를 구한다. (누적을 했던범위)
+ * - value
+ * ------ 의 방법으로 구할 예정인데
+ * time
+ *
+ * time이 결국 전체시간(LOAD_AVG_MAX)가 되겠지만
+ * sum을 했던 범위를 고려해 sum을 했었던 범위까지만 한정한다.
+ */
static inline u32 get_pelt_divider(struct sched_avg *avg)
{
return LOAD_AVG_MAX - 1024 + avg->period_contrib;
}
+/*
+ * IAMROOT, 2022.12.31:
+ * - Estimation 기능이 있는 경우만 수행한다.
+ * UTIL_AVG_UNCHANGED flag를 지운다.
+ */
static inline void cfs_se_util_change(struct sched_avg *avg)
{
unsigned int enqueued;
@@ -192,6 +207,15 @@ static inline void update_idle_rq_clock_pelt(struct rq *rq)
* this case. We keep track of this lost idle time compare to
* rq's clock_task.
*/
+/*
+ * IAMROOT, 2022.12.31:
+ * - papago
+ * 도난 시간을 반영하는 것은 유휴 단계가 최대 용량으로 존재할 경우에만 의미가
+ * 있습니다. rq의 활용도가 최대값에 도달하는 즉시 유휴 시간 없이 항상 실행 중인
+ * rq로 간주됩니다. 이 잠재적 유휴 시간은 이 경우 손실된 것으로 간주됩니다.
+ * 우리는 rq의 clock_task와 비교하여 이 손실된 유휴 시간을 추적합니다.
+.
+ */
if (util_sum >= divider)
rq->lost_idle_time += rq_clock_task(rq) - rq->clock_pelt;
}
@@ -199,7 +223,7 @@ static inline void update_idle_rq_clock_pelt(struct rq *rq)
/*
* IAMROOT, 2022.12.22:
* - clock_pelt의 실제 실행시간을 가져온다.
- * (cpu가 idle이엿을대의 시간을뺌)
+ * (cpu의 lost된 idle을 뺌)
*/
static inline u64 rq_clock_pelt(struct rq *rq)
{
diff --git a/kernel/sched/sched-pelt.h b/kernel/sched/sched-pelt.h
index c529706bed11..a91c88d60a10 100644
--- a/kernel/sched/sched-pelt.h
+++ b/kernel/sched/sched-pelt.h
@@ -1,6 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by Documentation/scheduler/sched-pelt; do not modify. */
+/*
+ * IAMROOT, 2022.12.31:
+ * - 1ms단위로 y^n에 대한 값이 [n]으로 들어가 있다.
+ * [32]에는 0.5가 들어 있는 개념(0x8000_0000)이 된다.(32은 필요없어서 없다)
+ */
static const u32 runnable_avg_yN_inv[] __maybe_unused = {
0xffffffff, 0xfa83b2da, 0xf5257d14, 0xefe4b99a, 0xeac0c6e6, 0xe5b906e6,
0xe0ccdeeb, 0xdbfbb796, 0xd744fcc9, 0xd2a81d91, 0xce248c14, 0xc9b9bd85,
@@ -11,4 +16,10 @@ static const u32 runnable_avg_yN_inv[] __maybe_unused = {
};
#define LOAD_AVG_PERIOD 32
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - y^32 = 0.5의 값인 y = 0.9786의 값에서
+ * y^n 일때 n = 0부터 무한대까지 합한 값.
+ */
#define LOAD_AVG_MAX 47742
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 75abd5082593..21700cd929c5 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -150,6 +150,11 @@ extern void call_trace_sched_update_nr_running(struct rq *rq, int count);
*/
# define NICE_0_LOAD_SHIFT (SCHED_FIXEDPOINT_SHIFT + SCHED_FIXEDPOINT_SHIFT)
# define scale_load(w) ((w) << SCHED_FIXEDPOINT_SHIFT)
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - 20bit로 scale up됫던걸 10bit로 변경한다.
+ */
# define scale_load_down(w) \
({ \
unsigned long __w = (w); \
@@ -688,6 +693,12 @@ struct cfs_rq {
* 포함합니다.
* idle_h_nr_running은 현재 실행 대기열에서 실행 중인 작업 수를 저장하는
* 부호 없는 정수이지만 유휴 스케줄링 정책이 있는 작업만 포함합니다.
+ *
+ * - nr_running
+ * cfs rq에서 현재 실행가능한 task 수
+ * - h_nr_running
+ * 하위 그룹까지 포함한 실행가능한 task 수.
+ * (throttled된 하위 cfs rq는 제외).
*/
unsigned int nr_running;
unsigned int h_nr_running; /* SCHED_{NORMAL,BATCH,IDLE} */
@@ -732,6 +743,12 @@ struct cfs_rq {
#ifndef CONFIG_64BIT
u64 load_last_update_time_copy;
#endif
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - cfs rq에서 se를 deattach(dequeue)할때 즉시 감소시키지 않고
+ * removed에 넣어놨다가 나중에 계산한다.update_cfs_rq_load_avg())
+ */
struct {
raw_spinlock_t lock ____cacheline_aligned;
int nr;
@@ -742,6 +759,14 @@ struct cfs_rq {
#ifdef CONFIG_FAIR_GROUP_SCHED
unsigned long tg_load_avg_contrib;
+
+/*
+ * IAMROOT, 2022.12.31:
+ * - propagate
+ * propagate를 해야되는지에 대한 flag.
+ * - prop_runnable_sum
+ * propagate을 위해 미리 누적해놓는값.
+ */
long propagate;
long prop_runnable_sum;
@@ -941,6 +966,13 @@ static inline void se_update_runnable(struct sched_entity *se)
se->runnable_weight = se->my_q->h_nr_running;
}
+/*
+ * IAMROOT, 2022.12.31:
+ * @return @se가 runable 상태인지.
+ *
+ * - @se가 task이면 rq에 들어가있을것이므로 on_req를 검사한다.
+ * task group인 경우엔 runnable_weight를 검사한다.
+ */
static inline long se_runnable(struct sched_entity *se)
{
if (entity_is_task(se))
@@ -1208,7 +1240,8 @@ struct rq {
u64 clock_pelt;
/*
* IAMROOT, 2022.12.22:
- * - cpu가 idle였을때 task를 실행하지 않은 시간.
+ * - cpu의 lost된 idle 시간의 적산.
+ * task를 실행하지 않은 시간.
*/
unsigned long lost_idle_time;
댓글 0
번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
---|---|---|---|---|
공지 | [공지] 스터디 정리 노트 공간입니다. | woos | 2016.05.14 | 617 |
182 | [커널 18차] 90주차 | kkr | 2023.02.13 | 63 |
181 | [커널 19차] 38 주차 | Min | 2023.02.11 | 44 |
180 | [커널 19차] 37 주차 | Min | 2023.02.04 | 478 |
179 | [커널 19차] 36 주차 | Min | 2023.01.28 | 85 |
178 | [커널 18차] 88주차 | kkr | 2023.01.28 | 54 |
177 | [커널 19차] 35 주차 | Min | 2023.01.14 | 93 |
176 | [커널 17차] 120 ~ 121주차 | ㅇㅇㅇ | 2023.01.08 | 110 |
175 | [커널 18차] 85주차 | kkr | 2023.01.07 | 53 |
174 | [커널 19차] 34 주차 | Min | 2023.01.07 | 42 |
» | [커널 18차] 84주차 | kkr | 2022.12.31 | 101 |
172 | [커널 19차] 33 주차 | Min | 2022.12.31 | 50 |
171 | [커널 17차] 117 ~ 119주차 | ㅇㅇㅇ | 2022.12.25 | 60 |
170 | [커널 19차] 31 주차 | Min | 2022.12.17 | 63 |
169 | [커널 19차] 30 주차 | Min | 2022.12.10 | 61 |
168 | [커널 17차] 112 ~ 116주차 | ㅇㅇㅇ | 2022.12.05 | 71 |
167 | [커널 18차] 80주차 | kkr | 2022.12.03 | 154 |
166 | [커널 19차] 28 ~ 29 주차 | Min | 2022.12.03 | 35 |
165 | [커널 19차] 27 주차 | Min | 2022.11.22 | 82 |
164 | [커널 18차] 78주차 | kkr | 2022.11.19 | 181 |
163 | [커널 19차] 25 ~ 26 주차 | Min | 2022.11.14 | 70 |
.