freezable 또는 freezer 등의 단어가 많이 보이는데 이게 무엇을 의미하는지 모르겠네요...
좀 더 구체적인 질문은 이렇습니다.
futex_wait_requeue_pi 의 소스코드를 보고 있는데, 이 함수가 uaddr1 -> uaddr2 로 requeue
되기 전까지 계속 wait 상태에 들어가는데 그 코드가
static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,
2546 u32 val, ktime_t *abs_time, u32 bitset,
2547 u32 __user *uaddr2)
2548 {
2549 struct hrtimer_sleeper timeout, *to = NULL;
2550 struct rt_mutex_waiter rt_waiter;
2551 struct rt_mutex *pi_mutex = NULL;
2552 struct futex_hash_bucket *hb;
2553 union futex_key key2 = FUTEX_KEY_INIT;
2554 struct futex_q q = futex_q_init;
2555 int res, ret;
2556
2557 if (uaddr == uaddr2)
2558 return -EINVAL;
2559
2560 if (!bitset)
2561 return -EINVAL;
2562
2563 if (abs_time) {
2564 to = &timeout;
2565 hrtimer_init_on_stack(&to->timer, (flags & FLAGS_CLOCKRT) ?
2566 CLOCK_REALTIME : CLOCK_MONOTONIC,
2567 HRTIMER_MODE_ABS);
2568 hrtimer_init_sleeper(to, current);
2569 hrtimer_set_expires_range_ns(&to->timer, *abs_time,
2570 current->timer_slack_ns);
2571 }
2572
2573 /*
2574 * The waiter is allocated on our stack, manipulated by the requeue
2575 * code while we sleep on uaddr.
2576 */
2577 debug_rt_mutex_init_waiter(&rt_waiter);
2578 RB_CLEAR_NODE(&rt_waiter.pi_tree_entry);
2579 RB_CLEAR_NODE(&rt_waiter.tree_entry);
2580 rt_waiter.task = NULL;
2581
2582 ret = get_futex_key(uaddr2, flags & FLAGS_SHARED, &key2, VERIFY_WRITE);
2583 if (unlikely(ret != 0))
2584 goto out;
2585
2586 q.bitset = bitset;
2587 q.rt_waiter = &rt_waiter;
2588 q.requeue_pi_key = &key2;
2589
2590 /*
2591 * Prepare to wait on uaddr. On success, increments q.key (key1) ref
2592 * count.
2593 */
2594 ret = futex_wait_setup(uaddr, val, flags, &q, &hb);
2595 if (ret)
2596 goto out_key2;
2597
2598 /*
2599 * The check above which compares uaddrs is not sufficient for
2600 * shared futexes. We need to compare the keys:
2601 */
2602 if (match_futex(&q.key, &key2)) {
2603 queue_unlock(hb);
2604 ret = -EINVAL;
2605 goto out_put_keys;
2606 }
2607
2608 /* Queue the futex_q, drop the hb lock, wait for wakeup. */
2609 futex_wait_queue_me(hb, &q, to);
저 위의 futex_wait_queue_me 에서 큐에 현재 uaddr1 / uaddr2 에 대한 정보를
넣고 대기로 빠지는 것으로 알고 있습니다.
그런데 futex_wait_queue_me 코드를 보면
2053 static void futex_wait_queue_me(struct futex_hash_bucket *hb, struct futex_q *q,
2054 struct hrtimer_sleeper *timeout)
2055 {
2056 /*
2057 * The task state is guaranteed to be set before another task can
2058 * wake it. set_current_state() is implemented using set_mb() and
2059 * queue_me() calls spin_unlock() upon completion, both serializing
2060 * access to the hash list and forcing another memory barrier.
2061 */
2062 set_current_state(TASK_INTERRUPTIBLE);
2063 queue_me(q, hb);
2064
2065 /* Arm the timer */
2066 if (timeout) {
2067 hrtimer_start_expires(&timeout->timer, HRTIMER_MODE_ABS);
2068 if (!hrtimer_active(&timeout->timer))
2069 timeout->task = NULL;
2070 }
2071
2072 /*
2073 * If we have been removed from the hash list, then another task
2074 * has tried to wake us, and we can skip the call to schedule().
2075 */
2076 if (likely(!plist_node_empty(&q->list))) {
2077 /*
2078 * If the timer has already expired, current will already be
2079 * flagged for rescheduling. Only call schedule if there
2080 * is no timeout, or if it has yet to expire.
2081 */
2082 if (!timeout || timeout->task)
2083 freezable_schedule();
2084 }
2085 __set_current_state(TASK_RUNNING);
2086 }
특별히 루프나 그런 건 없고, 그냥 freezable_schedule() 함수를 호출하고 있습니다.
그럼 이 함수 호출 후에, 어떤 특정 신호를 받거나 할 때까지 이 태스크는 계속 대기한다는
의미인데 원래 schedule() 함수는 스케줄링을 한 다음 다른 태스크에서 다시 현재 태스크로
스케줄링할 수도 있으니 계속 대기가 아니라 다음 스케줄링까지만 대기(?) 를 하게 되는걸로 알고 있는데
저 freezable_schedule 함수는 한 번 호출하면 계속 대기를 하는 건가요?
아예 스케줄링 큐에서 빠지는 함수인지, 그러면 다시 다른 태스크에서 현재 태스크를 스케줄링 큐에
넣을때까지는 계속 대기하는 원리라서 futex_cmp_requeue_pi (futex_requeue) 에서 특정 uaddr1 / uaddr2 에 대해
대기하고 있는 waiter 가 있으면 그 waiter 의 태스크를 다시 스케줄링 큐에 넣어준다던지 하는 게 있는 건가요?
저 freezable_schedule 의 코드를 보면, 커널 컴파일시에 CONFIG_FREEZER 라는 옵션이 만약 n 이면
그냥 schedule() 로 치환이 되고(저런 옵션은 본 적이 없지만..) y 인 경우는 아래처럼
168 static inline void freezable_schedule(void)
169 {
170 freezer_do_not_count();
171 schedule();
172 freezer_count();
173 }
이렇게 되고, freezer_do_not_count 가 무엇인지 보니
106 static inline void freezer_do_not_count(void)
107 {
108 current->flags |= PF_FREEZER_SKIP;
109 }
그냥 현재 태스크(thread_info) 의 flags 에 PF_FREEZER_SKIP 이라는 플래그를
설정해주는데.. 이게 무슨 역할을 하는 플래그인지 모르겠네요..
스케줄러 소스코드에서 이 플래그를 가지고 뭔가 하는거라도 있어야되는데 찾아보니
그것도 없고 그냥 /kernel/hung_task.c 에서만 뭔가 하거나 freezer.h 에만 조금 뭐 있는거같고..
음.. 질문이 좀 말이 어수선하게 되었는데 그냥 freezable_schedule() 이 하는 일이 무엇인지가 궁금합니다..
freezing 이란 말의 의미로 봐서는 말 그대로 태스크를 프리징시킨다 = 스케줄링하지 않는다 정도인거 같긴 한데..
찾아보면 꼭 그런거같지도 않고... 답변 부탁드리겠습니다.
댓글 2
-
째꾸
2015.03.14 06:38
-
CVE
2015.03.18 08:57
길고 자세한 답변 감사합니다.
그런데 제가 잘못 이해한 것이, freezable_schedule 을 써서 대기에 들어간게 아니라
앞의 set_current_state 에서 TASK_INTERRUPTIBLE 로 상태를 설정하고 나서 스케줄링을 했기 때문에
다시 스케줄링 되지 않는 것 같은데(예전 버전의 커널에선 그냥 schedule() 을 쓰고 있더군요.)
TASK_INTERRUPTIBLE 이 무슨 상태를 의미하는 것인가요?실제로 futex_requeue 나 또는 proxy mutex unlock 관련 함수를 보면 타겟 태스크에 대해
wake_up_state 함수에 TASK_NORMAL 을 줘서 깨우는 것으로 보이는데 그렇다면 정확히는
freezable_schedule 이든 schedule 이든 앞에서 태스크 상태를 변경 후에 스케줄링 했기 때문에
다시 스케줄링이 되지 않는다고 보는게 맞는거 같은데 이게 맞나요?
그리고 만약 이게 맞다면, 만약 preempt kernel 에서 set_current_state 를 호출하고 나서 바로
직후에 강제 스케줄링이 일어나버려서 위의 함수같은 경우 queue_me 나 기타 함수를
호출하지 못한 상태에서 스케줄링 되버리고, 다시 스케줄링이 되지 못한다고 하면 이건 문제가
될 거 같은데 이런 실제로 일이 일어날 수도 있는 건가요? 위의 futex_wait_queue_me 함수에선
딱히 preempt_disable 같은 함수가 보이지도 않으니....
복잡하네요.. 질문이 더 많아진거 같습니다.. 죄송합니다.
.
올해 스터디 모집 언제하는지 보려고 왔다가
댓글 처음 달아봅니다 ^^
freezable_schedule 같은 경우는 보고 계신대로 freezer_do_not_count를 호출하는 것 말고는
schedule과 동일합니다
freezer_do_not_count 는 power management 쪽에서
suspend to disk나 suspend to ram ( 윈도우로 말하면 최대절전모드와 대기모드 정도 되겠네요 )
를 구현할 때 사용됩니다
해당 모드 진입 전에 user process를 freezing 시키고 driver들을 suspend시키는 작업을 하게되는데
freezer_do_not_count가 설정된 user process freezing 시에 그것이 freezer_count로 바뀔 때까지
기다리는 역할을 수행합니다
예를 들어 위에 보시는 futex를 user level에서 mutex 등을 통해 걸고 있다면
lock이 풀릴 때까지 freezing이 지연되게 됩니다
너무 내용이 길어질 듯 하여 이만 하겠습니다
자세한건 혹시 더 궁금하시면 메일이나 여기 코멘트에 더 남겨주시면 말씀드리겠습니다
저보다 더 잘아시는 분들이 많을텐데 길게 얘기해서 죄송하네요..^^