[커널 18차] 78주차

2022.11.19 22:05

kkr 조회 수:187

softirq 완

 

git : https://github.com/iamroot18/5.10/commit/7695e8fe23629c8a8f73a6731f50d490e496688d

 

diff --git a/include/linux/percpu-rwsem.h b/include/linux/percpu-rwsem.h
index 5fda40f97fe9..887233140b07 100644
--- a/include/linux/percpu-rwsem.h
+++ b/include/linux/percpu-rwsem.h
@@ -44,6 +44,10 @@ is_static struct percpu_rw_semaphore name = {                \
 
 extern bool __percpu_down_read(struct percpu_rw_semaphore *, bool);
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - cpu on/off시 동기화 하기 위한 lock
+ */
 static inline void percpu_down_read(struct percpu_rw_semaphore *sem)
 {
     might_sleep();
diff --git a/include/linux/sched.h b/include/linux/sched.h
index bb20cd246c51..883b3353a48a 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -90,6 +90,12 @@ struct task_group;
 #define EXIT_ZOMBIE            0x0020
 #define EXIT_TRACE            (EXIT_ZOMBIE | EXIT_DEAD)
 /* Used in tsk->state again: */
+
+/*
+ * IAMROOT, 2022.11.19:
+ * - kthread에만 있는 개념.
+ * - wakeup업시 unpark할때까지 잠깐 멈추는 기능.
+ */
 #define TASK_PARKED            0x0040
 #define TASK_DEAD            0x0080
 #define TASK_WAKEKILL            0x0100
@@ -1694,8 +1700,9 @@ extern struct pid *cad_pid;
 #define PF_SIGNALED        0x00000400    /* Killed by a signal */
 /*
  * IAMROOT, 2022.04.16:
- * - task가 memory 회수 mode이면 set된다. memory 제한없이 사용할수있는 권한을 가질 수 있다.
- * - memory 회수 재귀등을 확인하는 flag로도 사용한다.
+ * - PF_MEMALLOC
+ *   task가 memory 회수 mode이면 set된다. memory 제한없이 사용할수있는 권한을 가질 수 있다.
+ *   memory 회수 재귀등을 확인하는 flag로도 사용한다.
  */
 #define PF_MEMALLOC        0x00000800    /* Allocating memory */
 #define PF_NPROC_EXCEEDED    0x00001000    /* set_user() noticed that RLIMIT_NPROC was exceeded */
diff --git a/kernel/cpu.c b/kernel/cpu.c
index 7f5de9bfd319..ab0db2b07cc9 100644
--- a/kernel/cpu.c
+++ b/kernel/cpu.c
@@ -307,6 +307,10 @@ static int cpu_hotplug_disabled;
 
 DEFINE_STATIC_PERCPU_RWSEM(cpu_hotplug_lock);
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - cpu on / off시 사용하는 lock
+ */
 void cpus_read_lock(void)
 {
     percpu_down_read(&cpu_hotplug_lock);
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
index f4e4cad52d6d..9a4aae4247a1 100644
--- a/kernel/irq/manage.c
+++ b/kernel/irq/manage.c
@@ -1698,7 +1698,7 @@ setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
  *   2. old action이 있는지 판단하여 shared irq인지 결정.
  *   3. shared 인지 아닌지 에 따라 flag검사 및, flag를  보며 irq_activate, enable_irq 결정
  *   4. thread_mask set 처리.
- *   5. onshot 관련 flag 처리.
+ *   5. oneshot 관련 flag 처리.
  *   6. irqt_stat에 생성된 irq번호로 자료구조 추가.
  *   7. /proc/에 생성된 irq번호로 자료구조 추가.
  *
diff --git a/kernel/kthread.c b/kernel/kthread.c
index 5b37a8567168..b28ba2661b21 100644
--- a/kernel/kthread.c
+++ b/kernel/kthread.c
@@ -233,6 +233,11 @@ void *kthread_probe_data(struct task_struct *task)
     return data;
 }
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - KTHREAD_SHOULD_PARK로 왔던걸 TASK_PARKED(schedule()) 한다.
+ *   unpark할때까지 깨어나도 계속 park상태로 잠들것이다.
+ */
 static void __kthread_parkme(struct kthread *self)
 {
     for (;;) {
@@ -353,6 +358,14 @@ static void create_kthread(struct kthread_create_info *create)
     }
 }
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - TODO
+ *  - thread 생성을위해 kthreadd를 wakeup해준다.
+ *  kthreadd에서 thread 생성이 완료 될때까지 wait한다.
+ *  thread는 동작중이 아니다. 후에 wakeup을 따로 해줘야한다.
+ *  (생성과 동시에 wakeup까지 하는건 kthread_run())
+ */
 static __printf(4, 0)
 struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
                             void *data, int node,
@@ -375,12 +388,31 @@ struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
     list_add_tail(&create->list, &kthread_create_list);
     spin_unlock(&kthread_create_lock);
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - kthreadd wakeup -> create_kthread()
+ *   -> int kthreads(void *_create)에서 생성 완료되면 done complete.
+ *
+ * - spawn_ksoftirqd 동작시 예
+ *  1. kthreadd에서 thread 생성 <-- 이부분
+ *  2. kthreadd에서 thread 생성완료 wait
+ *  3. set TASK_PARKED
+ *  4. wakeup smpboot_thread_fn()실행
+ */
     wake_up_process(kthreadd_task);
     /*
      * Wait for completion in killable state, for I might be chosen by
      * the OOM killer while kthreadd is trying to allocate memory for
      * new kernel thread.
      */
+/*
+ * IAMROOT, 2022.11.19:
+ * - spawn_ksoftirqd 동작시 예
+ *  1. kthreadd에서 thread 생성 
+ *  2. kthreadd에서 thread 생성완료 wait <-- 이부분
+ *  3. set TASK_PARKED
+ *  4. wakeup smpboot_thread_fn()실행
+ */
     if (unlikely(wait_for_completion_killable(&done))) {
         /*
          * If I was SIGKILLed before kthreadd (or new kernel thread)
@@ -441,6 +473,11 @@ struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
  *
  * Returns a task_struct or ERR_PTR(-ENOMEM) or ERR_PTR(-EINTR).
  */
+
+/*
+ * IAMROOT, 2022.11.19:
+ * - thread 생성
+ */
 struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
                        void *data, int node,
                        const char namefmt[],
@@ -508,6 +545,10 @@ EXPORT_SYMBOL(kthread_bind);
  *
  * Description: This helper function creates and names a kernel thread
  */
+/*
+ * IAMROOT, 2022.11.19:
+ * - thread 생성
+ */
 struct task_struct *kthread_create_on_cpu(int (*threadfn)(void *data),
                       void *data, unsigned int cpu,
                       const char *namefmt)
@@ -524,6 +565,10 @@ struct task_struct *kthread_create_on_cpu(int (*threadfn)(void *data),
     return p;
 }
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - @k에 @cpu id를 넣어주고 percpu thread라는걸 marking해준다.
+ */
 void kthread_set_per_cpu(struct task_struct *k, int cpu)
 {
     struct kthread *kthread = to_kthread(k);
@@ -558,6 +603,11 @@ bool kthread_is_per_cpu(struct task_struct *p)
  * waits for it to return. If the thread is marked percpu then its
  * bound to the cpu again.
  */
+/*
+ * IAMROOT, 2022.11.19:
+ * - unpark. smpboot_thread_fn()의 park측 참고.
+ *   park 상태에 있는것을 unpark후 깨운다.
+ */
 void kthread_unpark(struct task_struct *k)
 {
     struct kthread *kthread = to_kthread(k);
@@ -573,6 +623,11 @@ void kthread_unpark(struct task_struct *k)
     /*
      * __kthread_parkme() will either see !SHOULD_PARK or get the wakeup.
      */
+
+/*
+ * IAMROOT, 2022.11.19:
+ * - parked상태였으면 깨운다.
+ */
     wake_up_state(k, TASK_PARKED);
 }
 EXPORT_SYMBOL_GPL(kthread_unpark);
@@ -589,6 +644,11 @@ EXPORT_SYMBOL_GPL(kthread_unpark);
  * Returns 0 if the thread is parked, -ENOSYS if the thread exited.
  * If called by the kthread itself just the park bit is set.
  */
+/*
+ * IAMROOT, 2022.11.19:
+ * - TODO
+ *   @k를 park로 하고 wakeup한후 parked가 완료될때까지 기다린다.
+ */
 int kthread_park(struct task_struct *k)
 {
     struct kthread *kthread = to_kthread(k);
diff --git a/kernel/smpboot.c b/kernel/smpboot.c
index f6bc0bc8a2aa..57bd9ddf904d 100644
--- a/kernel/smpboot.c
+++ b/kernel/smpboot.c
@@ -103,6 +103,10 @@ enum {
  *
  * Returns 1 when the thread should exit, 0 otherwise.
  */
+/*
+ * IAMROOT, 2022.11.19:
+ * - thread가 run으로 되기전 동작하는 최초의 함수. park등의 state를 처리한다.
+ */
 static int smpboot_thread_fn(void *data)
 {
     struct smpboot_thread_data *td = data;
@@ -161,11 +165,20 @@ static int smpboot_thread_fn(void *data)
         } else {
             __set_current_state(TASK_RUNNING);
             preempt_enable();
+
+/*
+ * IAMROOT, 2022.11.19:
+ * - 실제 등록된 thread_fn
+ */
             ht->thread_fn(td->cpu);
         }
     }
 }
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - thread 생성 및 park.
+ */
 static int
 __smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
 {
@@ -175,12 +188,24 @@ __smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
     if (tsk)
         return 0;
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - 자료구조를 만들어서 기본정보를 넣어준다.
+ */
     td = kzalloc_node(sizeof(*td), GFP_KERNEL, cpu_to_node(cpu));
     if (!td)
         return -ENOMEM;
     td->cpu = cpu;
     td->ht = ht;
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - 해당 함수에서 1 ~ 2번까지 진행한다.
+ *  1. kthreadd에서 thread 생성
+ *  2. kthreadd에서 thread 생성완료 wait
+ *  3. set TASK_PARKED
+ *  4. wakeup smpboot_thread_fn()실행
+ */
     tsk = kthread_create_on_cpu(smpboot_thread_fn, td, cpu,
                     ht->thread_comm);
     if (IS_ERR(tsk)) {
@@ -192,6 +217,16 @@ __smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
      * Park the thread so that it could start right on the CPU
      * when it is available.
      */
+/*
+ * IAMROOT, 2022.11.19:
+ * - spawn_ksoftirqd 동작시 예
+ *  1. kthreadd에서 thread 생성 
+ *  2. kthreadd에서 thread 생성완료 wait 
+ *  3 ~ 4번이 해당함수에서 실행. 5번은 sch이 알아서 실행.
+ *  3. set TASK_PARKED 
+ *  4. wakeup smpboot_thread_fn
+ *  5. smpboot_thread_fn()실행 -> park 상태로 schedule(). 
+ */
     kthread_park(tsk);
     get_task_struct(tsk);
     *per_cpu_ptr(ht->store, cpu) = tsk;
@@ -225,6 +260,10 @@ int smpboot_create_threads(unsigned int cpu)
     return ret;
 }
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - unpark후 깨운다.
+ */
 static void smpboot_unpark_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
 {
     struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);
@@ -286,6 +325,11 @@ static void smpboot_destroy_threads(struct smp_hotplug_thread *ht)
  *
  * Creates and starts the threads on all online cpus.
  */
+/*
+ * IAMROOT, 2022.11.19:
+ * - cpu마다 @plug_thread의 thread를 생성하고 hotplug_threads를 @plug_thread에 넣어준다.
+ * - thread 생성 -> parked -> unparked -> thread_fn실행
+ */
 int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread)
 {
     unsigned int cpu;
@@ -293,6 +337,7 @@ int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread)
 
     cpus_read_lock();
     mutex_lock(&smpboot_threads_lock);
+
     for_each_online_cpu(cpu) {
         ret = __smpboot_create_thread(plug_thread, cpu);
         if (ret) {
diff --git a/kernel/softirq.c b/kernel/softirq.c
index cba80841de7d..d9a3900311fb 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -33,6 +33,19 @@
 #define CREATE_TRACE_POINTS
 #include <trace/events/irq.h>
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - open_softirq
+ * - spawn_ksoftirqd
+ * - raise_softirq
+ * - invoke_softirq
+ *         __do_softirq or run_ksoftirqd
+ * - __irq_exit_rcu
+ *         __do_softirq
+ * - run_ksoftirqd
+ *         __do_softirq
+ */
+
 /*
    - No shared variables, all the data are CPU local.
    - If a softirq needs serialization, let it serialize itself
@@ -58,6 +71,10 @@ EXPORT_PER_CPU_SYMBOL(irq_stat);
 
 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - 
+ */
 DEFINE_PER_CPU(struct task_struct *, ksoftirqd);
 
 const char * const softirq_to_name[NR_SOFTIRQS] = {
@@ -175,6 +192,10 @@ bool local_bh_blocked(void)
     return __this_cpu_read(softirq_ctrl.cnt) != 0;
 }
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - PREEMPT_RT
+ */
 void __local_bh_disable_ip(unsigned long ip, unsigned int cnt)
 {
     unsigned long flags;
@@ -290,6 +311,7 @@ EXPORT_SYMBOL(__local_bh_enable_ip);
 /*
  * IAMROOT, 2022.11.12:
  * - preempt.
+ *   irq disable.
  */
 static inline void ksoftirqd_run_begin(void)
 {
@@ -298,6 +320,10 @@ static inline void ksoftirqd_run_begin(void)
 }
 
 /* Counterpart to ksoftirqd_run_begin() */
+/*
+ * IAMROOT, 2022.11.19:
+ * - irq enable
+ */
 static inline void ksoftirqd_run_end(void)
 {
     __local_bh_enable(SOFTIRQ_OFFSET, true);
@@ -305,6 +331,10 @@ static inline void ksoftirqd_run_end(void)
     local_irq_enable();
 }
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - PREEMPT_RT
+ */
 static inline void softirq_handle_begin(void) { }
 static inline void softirq_handle_end(void) { }
 
@@ -425,6 +455,10 @@ void __local_bh_enable_ip(unsigned long ip, unsigned int cnt)
 }
 EXPORT_SYMBOL(__local_bh_enable_ip);
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - !PREEMPT
+ */
 static inline void softirq_handle_begin(void)
 {
     __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
@@ -569,7 +603,10 @@ static inline void lockdep_softirq_end(bool in_hardirq) { }
 
 /*
  * IAMROOT, 2022.11.12:
- * - TODO
+ * - pending softirq를 처리한다.
+ *   pending 처리후 2ms가 초과하면 ksoftirqd를 깨운다.
+ * - softirq는 저런 시간 처리때문에 sleep을 쓰면안된다. 메모리할당도 빠르게 하기 위해
+ *   GFP_ATOMIC등을 사용한다.
  */
 asmlinkage __visible void __softirq_entry __do_softirq(void)
 {
@@ -586,10 +623,24 @@ asmlinkage __visible void __softirq_entry __do_softirq(void)
      * softirq. A softirq handled, such as network RX, might set PF_MEMALLOC
      * again if the socket is related to swapping.
      */
+/*
+ * IAMROOT, 2022.11.19:
+ * - papago
+ *   현재 작업 컨텍스트가 softirq에 대해 차용되므로 PF_MEMALLOC를 마스킹합니다. 네트워크 RX와
+ *   같이 처리된 softirq는 소켓이 스와핑과 관련된 경우 PF_MEMALLOC를 다시 설정할 수 있습니다.
+ */
     current->flags &= ~PF_MEMALLOC;
 
     pending = local_softirq_pending();
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - PREEMPT
+ *   이미 bh disable이 되있다.
+ *
+ * - !PREEMPT
+ *   이 시점에서 bh disable 수행
+ */
     softirq_handle_begin();
     in_hardirq = lockdep_softirq_start();
     account_softirq_enter(current);
@@ -598,6 +649,11 @@ asmlinkage __visible void __softirq_entry __do_softirq(void)
     /* Reset the pending bitmask before enabling irqs */
     set_softirq_pending(0);
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - irq enable.
+ *   pending bit를 가져온후 reset을 정확히 하기위해 잠시동안만 off를 했었다.
+ */
     local_irq_enable();
 
     h = softirq_vec;
@@ -614,6 +670,11 @@ asmlinkage __visible void __softirq_entry __do_softirq(void)
         kstat_incr_softirqs_this_cpu(vec_nr);
 
         trace_softirq_entry(vec_nr);
+
+/*
+ * IAMROOT, 2022.11.19:
+ * - open_softirq에서 등록된 action handler 처리
+ */
         h->action(h);
         trace_softirq_exit(vec_nr);
         if (unlikely(prev_count != preempt_count())) {
@@ -630,8 +691,19 @@ asmlinkage __visible void __softirq_entry __do_softirq(void)
         __this_cpu_read(ksoftirqd) == current)
         rcu_softirq_qs();
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - irq disable.
+ */
     local_irq_disable();
 
+
+/*
+ * IAMROOT, 2022.11.19:
+ * - hard irq에서 동작하다가 시간이 너무 길어진 경우 동작한다.
+ *   (MAX_SOFTIRQ_TIME. 2ms)이내인 경우 restart로 처리하고
+ *   그게 아니면 ksoftirqd 에서 처리한다.
+ */
     pending = local_softirq_pending();
     if (pending) {
         if (time_before(jiffies, end) && !need_resched() &&
@@ -759,6 +831,7 @@ void irq_exit(void)
 /*
  * IAMROOT, 2022.11.12:
  * - pending을 일단하고 현재 context가 kernel thread인 경우는 바로 깨운다.
+ *   interrupt context에 있을때에는 irq가 끝낫을시에 처리한다. (__irq_exit_rcu())
  */
 inline void raise_softirq_irqoff(unsigned int nr)
 {
@@ -816,6 +889,20 @@ void __raise_softirq_irqoff(unsigned int nr)
 /*
  * IAMROOT, 2022.09.03:
  * - softirq @nr에 대한 @action함수를 등록한다.
+ * - 우선순위에 따라 동작한다.
+ *   --- 우선순위 높음---
+ *   open_softirq(HI_SOFTIRQ, tasklet_hi_action);
+ *   open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
+ *   open_softirq(NET_TX_SOFTIRQ, net_tx_action);
+ *   open_softirq(NET_RX_SOFTIRQ, net_rx_action);
+ *   open_softirq(BLOCK_SOFTIRQ, blk_done_softirq);
+ *   open_softirq(IRQ_POLL_SOFTIRQ, irq_poll_softirq);
+ *   open_softirq(TASKLET_SOFTIRQ, tasklet_action);
+ *   open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);
+ *   open_softirq(HRTIMER_SOFTIRQ, hrtimer_run_softirq);
+ *   open_softirq(RCU_SOFTIRQ, rcu_process_callbacks); open_softirq(RCU_SOFTIRQ, rcu_core_si);
+ *   (RCU_SOFTIRQ는 조건에따라서 동작이 다르다)
+ *   --- 우선순위 낮음---
  */
 void open_softirq(int nr, void (*action)(struct softirq_action *))
 {
@@ -1027,6 +1114,10 @@ static int ksoftirqd_should_run(unsigned int cpu)
     return local_softirq_pending();
 }
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - irq disable. 후 softirq 처리.
+ */
 static void run_ksoftirqd(unsigned int cpu)
 {
     ksoftirqd_run_begin();
@@ -1044,6 +1135,11 @@ static void run_ksoftirqd(unsigned int cpu)
 }
 
 #ifdef CONFIG_HOTPLUG_CPU
+/*
+ * IAMROOT, 2022.11.19:
+ * - TODO
+ * - head -> tail을 연결한다.
+ */
 static int takeover_tasklets(unsigned int cpu)
 {
     /* CPU is dead, so no lock needed. */
@@ -1074,12 +1170,26 @@ static int takeover_tasklets(unsigned int cpu)
 #endif /* CONFIG_HOTPLUG_CPU */
 
 static struct smp_hotplug_thread softirq_threads = {
+
+/*
+ * IAMROOT, 2022.11.19:
+ * - DEFINE_PER_CPU(struct task_struct *, ksoftirqd);
+ */
     .store            = &ksoftirqd,
     .thread_should_run    = ksoftirqd_should_run,
     .thread_fn        = run_ksoftirqd,
+/*
+ * IAMROOT, 2022.11.19:
+ * - %u : cpu number
+ */
     .thread_comm        = "ksoftirqd/%u",
 };
 
+/*
+ * IAMROOT, 2022.11.19:
+ * - takeover_tasklets를 CPUHP_SOFTIRQ_DEAD에 등록한다.
+ *   cpu가 offline으로 동작할때 실행될것이다.
+ */
 static __init int spawn_ksoftirqd(void)
 {
     cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
 

번호 제목 글쓴이 날짜 조회 수
공지 [공지] 스터디 정리 노트 공간입니다. woos 2016.05.14 626
167 [커널 18차] 80주차 kkr 2022.12.03 156
166 [커널 19차] 28 ~ 29 주차 Min 2022.12.03 35
165 [커널 19차] 27 주차 Min 2022.11.22 82
» [커널 18차] 78주차 kkr 2022.11.19 187
163 [커널 19차] 25 ~ 26 주차 Min 2022.11.14 72
162 [커널 18차] 76-77주차 kkr 2022.11.12 386
161 [커널 19차] 24주차 Min 2022.10.31 108
160 [커널 17차] 112주차 ㅇㅇㅇ 2022.10.30 81
159 [커널 18차] 75주차 kkr 2022.10.29 40
158 [커널 17차] 107 ~ 111주차 ㅇㅇㅇ 2022.10.23 77
157 [커널 19차] 22주차 Min 2022.10.17 76
156 [커널 18차] 73주차 kkr 2022.10.15 46
155 [커널 18차] 72주차 kkr 2022.10.09 183
154 [커널 18차] 71주차 kkr 2022.10.01 75
153 [커널 18차] 70주차 kkr 2022.09.24 77
152 [커널 18차] 69주차 kkr 2022.09.22 58
151 [커널 17차] 105~106주차 ㅇㅇㅇ 2022.09.18 50
150 [커널 17차] 104주차 ㅇㅇㅇ 2022.09.04 88
149 [커널 18차] 67주차 kkr 2022.09.03 138
148 [커널 17차] 103주차 ㅇㅇㅇ 2022.08.28 35
XE Login