리눅스 시스템 차원의 서스펜드 또는 하이버네이션 기능으로 현재 동작중인 모든 유저 스레드와 몇 개의 커널 스레드들을 얼려 정지시킨다.
- 유저 스레드들은 시그널 핸들링 코드에 의해 try_to_freeze() 호출이 되면서 freeze가 된다.
- 커널 스레드들은 명확하게 적절한 위치에서 freeze 요청을 확인하고 호출해줘야 한다.
- freeze 불가능한 작업큐에 있는 태스크들이 완료될 때 까지 대기하는데 딜레이 된 태스크들에 대해서는 전부 active 시킨 후 대기한다.
Freeze
freezing()
include/linux/freezer.h
/*
* Check if there is a request to freeze a process
*/
static inline bool freezing(struct task_struct *p)
{
if (likely(!atomic_read(&system_freezing_cnt)))
return false;
return freezing_slow_path(p);
}
현재 태스크가 freeze 상태로 들어갈 수 있는지 여부를 확인하여 반환한다. (true=freeze 가능, false=freeze 불가능)
- 코드 라인 06~07에서 높은 확률로 전역 system_freezing_cnt가 0인 경우 freeze 요청이 없으므로 false를 반환한다.
- 코드 라인 08에서 현재 태스크가 freeze 상태로 들어갈 수 있는지 여부를 확인하여 반환한다.
freezing_slow_path()
kernel/freezer.c
/**
* freezing_slow_path - slow path for testing whether a task needs to be frozen
* @p: task to be tested
*
* This function is called by freezing() if system_freezing_cnt isn't zero
* and tests whether @p needs to enter and stay in frozen state. Can be
* called under any context. The freezers are responsible for ensuring the
* target tasks see the updated state.
*/
bool freezing_slow_path(struct task_struct *p)
{
if (p->flags & (PF_NOFREEZE | PF_SUSPEND_TASK))
return false;
if (test_thread_flag(TIF_MEMDIE))
return false;
if (pm_nosig_freezing || cgroup_freezing(p))
return true;
if (pm_freezing && !(p->flags & PF_KTHREAD))
return true;
return false;
}
EXPORT_SYMBOL(freezing_slow_path);
현재 태스크가 freeze 상태로 들어갈 수 있는지 여부를 확인하여 반환한다.
- 코드 라인 12~13에서 현재 태스크에 PF_NOFREEZE 또는 PF_SUSPEND_TASK 플래그가 설정된 경우 freeze 하지 못하게 false를 반환한다.
- 코드 라인 15~16에서 현재 태스크에 TIF_MEMDIE 플래그가 있는 경우 freeze 하지 못하게 false를 반환한다.
- 코드 라인18~19에서 전역 pm_nosig_freezing이 true이거나 현재 태스크에 대해 cgroup에 freezing이 설정된 경우 freezing을 할 수 있게 true를 반환한다.
- 코드 라인 21~22에서 전역 pm_freezing이 true이면서 현재 태스크가 커널 스레드가 아닌 경우 freezing을 할 수 있게 true를 반환한다.
try_to_freeze_tasks()
kernel/power/process.c
static int try_to_freeze_tasks(bool user_only)
{
struct task_struct *g, *p;
unsigned long end_time;
unsigned int todo;
bool wq_busy = false;
struct timeval start, end;
u64 elapsed_msecs64;
unsigned int elapsed_msecs;
bool wakeup = false;
int sleep_usecs = USEC_PER_MSEC;
do_gettimeofday(&start);
end_time = jiffies + msecs_to_jiffies(freeze_timeout_msecs);
if (!user_only)
freeze_workqueues_begin();
while (true) {
todo = 0;
read_lock(&tasklist_lock);
for_each_process_thread(g, p) {
if (p == current || !freeze_task(p))
continue;
if (!freezer_should_skip(p))
todo++;
}
read_unlock(&tasklist_lock);
if (!user_only) {
wq_busy = freeze_workqueues_busy();
todo += wq_busy;
}
if (!todo || time_after(jiffies, end_time))
break;
if (pm_wakeup_pending()) {
wakeup = true;
break;
}
/*
* We need to retry, but first give the freezing tasks some
* time to enter the refrigerator. Start with an initial
* 1 ms sleep followed by exponential backoff until 8 ms.
*/
usleep_range(sleep_usecs / 2, sleep_usecs);
if (sleep_usecs < 8 * USEC_PER_MSEC)
sleep_usecs *= 2;
}
do_gettimeofday(&end);
elapsed_msecs64 = timeval_to_ns(&end) - timeval_to_ns(&start);
do_div(elapsed_msecs64, NSEC_PER_MSEC);
elapsed_msecs = elapsed_msecs64;
if (todo) {
pr_cont("\n");
pr_err("Freezing of tasks %s after %d.%03d seconds "
"(%d tasks refusing to freeze, wq_busy=%d):\n",
wakeup ? "aborted" : "failed",
elapsed_msecs / 1000, elapsed_msecs % 1000,
todo - wq_busy, wq_busy);
if (!wakeup) {
read_lock(&tasklist_lock);
for_each_process_thread(g, p) {
if (p != current && !freezer_should_skip(p)
&& freezing(p) && !frozen(p))
sched_show_task(p);
}
read_unlock(&tasklist_lock);
}
} else {
pr_cont("(elapsed %d.%03d seconds) ", elapsed_msecs / 1000,
elapsed_msecs % 1000);
}
return todo ? -EBUSY : 0;
}
유저 스레드 및 커널 스레드들을 freeze 시도한다. 정상적으로 freeze한 경우 0을 반환하고 그렇지 않은 경우 -EBUSY를 반환한다. 인수로 user_only가 false인 경우 freeze 불가능한 작업큐에서 동작하는 태스크와 지연된 태스크들에 대해 모두 처리가 완료될 때까지 최대 20초를 기다린다.
- 코드 라인 15에서 현재 시간으로 부터 20초를 타임아웃으로 설정한다.
- freeze_timeout_msecs
- 20 * MSEC_PER_SEC;
- freeze_timeout_msecs
- 코드 라인 17~18에서 user_only가 false인 경우 freeze 할 수 없는 워크 큐에 있는 지연된 태스크들을 모두 pool 워크큐로 옮기고 activate 시킨다.
- 코드 라인 20~30에서 freeze되지 않고 아직 남아 있는 스레드 수를 todo로 산출한다.
- 모든 스레드들에서 현재 태스크와 freeze되지 않은 태스크들에 대해 skip 한다.
- freeze된 스레드들에서 freezer가 skip 해야 하는 경우가 아니었다면 todo를 증가시킨다.
- 코드 라인 32~35에서 pool 워크큐에서 여전히 activate 된 수를 todo에 더한다.
- user_only가 false인 경우 pool 워크큐가 여전히 active 된 태스크들이 존재하면 todo를 증가시킨다.
- 코드 라인 37~38에서 처리할 항목이 없거나 타임 오버된 경우 루프를 탈출한다.
- 코드 라인 40~43에서 suspend(freeze)가 포기된 경우 wakeup 준비한다.
- 코드 라인 50~53에서 sleep_usecs의 절반 ~ sleep_usecs 범위에서 sleep 한 후 sleep_usces가 8ms 미만인 경우 sleep_usecs를 2배로 키운다.
- 처음 sleep_usecs 값은 1000으로 1ms이다.
- 코드 라인 55~58에서 소요 시간을 산출하여 elapsed_msecs에 저장한다.
- 코드 라인 60~76에서 freeze 되지 않은 항목이 남아 있는 경우 이에 대한 에러 출력을 하고 해당 스레드에 대한 정보를 자세히 출력한다.
기타
다음은 freeze와 관련된 함수이다.
- freeze_processes()
- freeze_kernel_threads()
- thaw_processes()
참고