본문 바로가기

Linux(Centos or RHEL)/RHEL 기초

프로세스를 TASK_UNINTERRUPTIBLE 상태로 바꾸는 함수...?

프로세스를 TASK_UNINTERRUPTIBLE 상태로 바꿀 때 호출되는 함수들에 대해서 공부를 해 보았습니다.

커널 버전은 v2.6.39.4 입니다.

 

아래 블로그를 참고하였습니다.

http://rousalome.egloos.com/10003493

 

[리눅스커널] 스케줄링: TASK_INTERRUPTIBLE 상태로 바뀔 때 호출되는 함수

TASK_UNINTERRUPTIBLE 상태로 바뀔 때 호출하는 함수 분석 다음 함수가 호출될 때 프로세스 상태를 TASK_UNINTERRUPTIBLE로 바꿉니다.  io_wait_event()  mutex_lock()   usleep_range()   msleep()  wait_for_completion() io_wait_eve

rousalome.egloos.com

 

프로세스의 TASK_UNINTERRUPTIBLE 상태는 다음과 같은 상태를 나타냅니다.

- 프로세스가 휴면 상태로 진입하지만, interrupt로 깨우지 못하는 상태입니다.

- 리눅스의 ps -ely 명령어로 보이는 D 상태의 프로세스입니다.

- 프로세스가 특정 조건으로 깨어나고 싶을 때 설정하는 상태입니다.

- 보통 스스로 깨어날 조건을 설정한 다음에 TASK_UNINTERRUPTIBLE 상태로 변경합니다.

- 뮤텍스를 얻지 못하거나 I/O 동작 중에 TASK_UNINTERRUPTIBLE 상태로 변경합니다.

 

 

프로세스의 상태를 TASK_UNINTERRUPTIBLE로 바꾸는 함수는 아래와 같은 것들이 있습니다.

(1) wait_for_completion() 함수들

(2) sleep_on() 함수들

(3) mutex_lock() 함수들

(4) wait_event() 함수들

(5) schedule_timeout_uninterruptible() 함수

(6) msleep() 함수

(7) usleep_range() 함수

 

 

[https://elixir.bootlin.com/linux/v2.6.39.4/source/kernel/sched.c#L4482]

(1) wait_for_completion() 함수들

“kernel/sched.c“에 정의된 함수들로, wait_for_completion(), wait_for_completion_timeout() 함수가 있습니다.

 

  1] wait_for_completion() 함수

/**
 * wait_for_completion: - waits for completion of a task
 * @x:  holds the state of this particular completion
 *
 * This waits to be signaled for completion of a specific task. It is NOT
 * interruptible and there is no timeout.
 *
 * See also similar routines (i.e. wait_for_completion_timeout()) with timeout
 * and interrupt capability. Also see complete().
 */
void __sched wait_for_completion(struct completion *x)
{
	wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion);

 

 

    2] wait_for_completion_timeout() 함수

/**
 * wait_for_completion_timeout: - waits for completion of a task (w/timeout)
 * @x:  holds the state of this particular completion
 * @timeout:  timeout value in jiffies
 *
 * This waits for either a completion of a specific task to be signaled or for a
 * specified timeout to expire. The timeout is in jiffies. It is not
 * interruptible.
 */
unsigned long __sched
wait_for_completion_timeout(struct completion *x, unsigned long timeout)
{
	return wait_for_common(x, timeout, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion_timeout);

 

 

위의 함수들은 공통적으로 wait_for_common() 함수를 호출하는데, 이 함수는 또 do_wait_for_common()함수를 부르는 것을 알 수 있습니다. 그리고 불려진 do_wait_for_common()함수에서 __set_current_state()함수를 통하여 프로세스의 상태를 변경하는 것을 확인할 수 있습니다.

 

[https://elixir.bootlin.com/linux/v2.6.39.4/source/kernel/sched.c#L4447]

 

sched.c - kernel/sched.c - Linux source code (v2.6.39.4) - Bootlin

/* * kernel/sched.c * * Kernel scheduler and related syscalls * * Copyright (C) 1991-2002 Linus Torvalds * * 1996-12-23 Modified by Dave Grothe to fix bugs in semaphores and * make semaphores SMP safe * 1998-11-19 Implemented schedule_timeout() and related

elixir.bootlin.com

 

 

 

[https://elixir.bootlin.com/linux/v2.6.39.4/source/kernel/sched.c#L4627]

(2) sleep_on() 함수들

“kernel/sched.c“에 정의된 함수들로, sleep_on(), sleep_on_timeout() 함수가 있습니다.

 

  1] sleep_on() 함수

void __sched sleep_on(wait_queue_head_t *q)
{
	sleep_on_common(q, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
EXPORT_SYMBOL(sleep_on);

 

  2] sleep_on_timeout() 함수

long __sched sleep_on_timeout(wait_queue_head_t *q, long timeout)
{
	return sleep_on_common(q, TASK_UNINTERRUPTIBLE, timeout);
}
EXPORT_SYMBOL(sleep_on_timeout);

 

위의 함수들은 공통적으로 sleep_on_common()함수를 호출합니다. 불려진 sleep_on_common()함수에서 __set_current_state() 함수를 호출하여 프로세스의 상태를 바꿉니다.

 

 

 

 

 

[https://elixir.bootlin.com/linux/v2.6.39.4/source/kernel/mutex.c#L276]

(3) mutex_lock() 함수들

“kernel/mutex.c“에 정의된 함수들로, mutex_lock_nested(), __mutex_lock_slowpath() 함수가 있습니다.

 

  1] mutex_lock_nested() 함수

아래 코드를 보면 커널이 ”CONFIG_DEBUG_LOCK_ALLOC“옵션이 설정되어 있을 경우에 정의되는 함수로 나와 있습니다. 해당 파라미터가 설정이 되어있는가를 확인하는 것은, CentOS에서는, ”/boot/config-<커널 버전>“의 파일을 보면 확인할 수 있습니다. 디폴트로는 설정되지 않는 것 같습니다.

 

#ifdef CONFIG_DEBUG_LOCK_ALLOC
void __sched
mutex_lock_nested(struct mutex *lock, unsigned int subclass)
{
	might_sleep();
	__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, subclass, _RET_IP_);
}

 

 

  2] __mutex_lock_slowpath() 함수

mutex에 대해서 간단히 설명하자면, mutexMUTual Exclusion의 약어로, 상호 배제라고도 합니다. 여러 프로세스 및 스레드에서 접근이 가능한 구역(임계 영역)mutex를 획득한 프로세스 혹은 스레드만이 접근이 가능하게 만듭니다. 임계 영역에 접근한 프로세스나 스레드는 해당 영역을 자기만 사용하기 위해서 락을 겁니다.

프로세스나 스레드가 mutex를 획득하는 방법은 3가지가 있는데, fastpath, midpath, slowpath입니다. 각 방법은 임계 영역의 락 상태에 따라서 사용됩니다. 그 중 프로세스의 상태를 TASK_UNINTERRUPTIBEL로 바꾸는 slowpath방식을 간단히 설명하면, 해당 임계 영역에 락이 걸려 있을 경우, 프로세스는 mutex를 얻을 때까지 루프를 돌며 sleep하고, 다른 프로세스로 CPU의 실행권을 넘깁니다.

mutex에 대한 자세한 내용은 아래의 링크들을 참조하면 좋을 것 같습니다.

[http://jake.dothome.co.kr/mutex/]

[https://ninako21.tistory.com/500]

[https://bitsoul.tistory.com/172]

 

static __used noinline void __sched
__mutex_lock_slowpath(atomic_t *lock_count)
{
	struct mutex *lock = container_of(lock_count, struct mutex, count);

	__mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, 0, _RET_IP_);
}

 

 

위 함수들은 공통적으로 __mutex_lock_common()함수를 호출합니다. 불려진 __mutex_lock_common()함수에서는 __set_task_state()함수를 이용하여 프로세스의 상태를 변경합니다.

https://elixir.bootlin.com/linux/v2.6.39.4/source/kernel/mutex.c#L133

 

mutex.c - kernel/mutex.c - Linux source code (v2.6.39.4) - Bootlin

/* * kernel/mutex.c * * Mutexes: blocking mutual exclusion locks * * Started by Ingo Molnar: * * Copyright (C) 2004, 2005, 2006 Red Hat, Inc., Ingo Molnar * * Many thanks to Arjan van de Ven, Thomas Gleixner, Steven Rostedt and * David Howells for suggesti

elixir.bootlin.com

 

 

 

 

 

 

[https://elixir.bootlin.com/linux/v2.6.39.4/source/include/linux/wait.h#L192]

(4) wait_event() 함수들

 

  1] wait_event() 함수

매크로 함수로써 정의되어 있습니다. 이 함수는 __wait_event() 매크로 함수를 부르는데, 불려진 __wait_event() 매크로 함수에서 prepare_to_wait() 함수를 이용하여 프로세스의 상태를 변경합니다.

 

#define __wait_event(wq, condition) 					\
do {									\
	DEFINE_WAIT(__wait);						\
									\
	for (;;) {							\
		prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);	\
		if (condition)						\
			break;						\
		schedule();						\
	}								\
	finish_wait(&wq, &__wait);					\
} while (0)

/**
 * wait_event - sleep until a condition gets true
 * @wq: the waitqueue to wait on
 * @condition: a C expression for the event to wait for
 *
 * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the
 * @condition evaluates to true. The @condition is checked each time
 * the waitqueue @wq is woken up.
 *
 * wake_up() has to be called after changing any variable that could
 * change the result of the wait condition.
 */
#define wait_event(wq, condition) 					\
do {									\
	if (condition)	 						\
		break;							\
	__wait_event(wq, condition);					\
} while (0)

 

 

  2] wait_event_timeout() 함수

매크로 함수로써 정의되어 있습니다. 이 함수는 __wait_event_timeout() 매크로 함수를 부르는데, 불려진 __wait_event_timeout() 매크로 함수에서 prepare_to_wait() 함수를 이용하여 프로세스의 상태를 변경합니다.

 

#define __wait_event_timeout(wq, condition, ret)			\
do {									\
	DEFINE_WAIT(__wait);						\
									\
	for (;;) {							\
		prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);	\
		if (condition)						\
			break;						\
		ret = schedule_timeout(ret);				\
		if (!ret)						\
			break;						\
	}								\
	finish_wait(&wq, &__wait);					\
} while (0)

/**
 * wait_event_timeout - sleep until a condition gets true or a timeout elapses
 * @wq: the waitqueue to wait on
 * @condition: a C expression for the event to wait for
 * @timeout: timeout, in jiffies
 *
 * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the
 * @condition evaluates to true. The @condition is checked each time
 * the waitqueue @wq is woken up.
 *
 * wake_up() has to be called after changing any variable that could
 * change the result of the wait condition.
 *
 * The function returns 0 if the @timeout elapsed, and the remaining
 * jiffies if the condition evaluated to true before the timeout elapsed.
 */
#define wait_event_timeout(wq, condition, timeout)			\
({									\
	long __ret = timeout;						\
	if (!(condition)) 						\
		__wait_event_timeout(wq, condition, __ret);		\
	__ret;								\
})

 

 

위 두 함수들에서 공통적으로 부르는 함수인 prepare_to_wait()set_current_state() 함수를 이용하여 프로세스의 상태를 변경합니다.

[https://elixir.bootlin.com/linux/v2.6.39.4/source/kernel/wait.c#L68]

/*
 * Note: we use "set_current_state()" _after_ the wait-queue add,
 * because we need a memory barrier there on SMP, so that any
 * wake-function that tests for the wait-queue being active
 * will be guaranteed to see waitqueue addition _or_ subsequent
 * tests in this thread will see the wakeup having taken place.
 *
 * The spin_unlock() itself is semi-permeable and only protects
 * one way (it only protects stuff inside the critical region and
 * stops them from bleeding out - it would still allow subsequent
 * loads to move into the critical region).
 */
void
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
	unsigned long flags;

	wait->flags &= ~WQ_FLAG_EXCLUSIVE;
	spin_lock_irqsave(&q->lock, flags);
	if (list_empty(&wait->task_list))
		__add_wait_queue(q, wait);
	set_current_state(state);
	spin_unlock_irqrestore(&q->lock, flags);
}
EXPORT_SYMBOL(prepare_to_wait);

 

 

 

 

[https://elixir.bootlin.com/linux/v2.6.39.4/source/kernel/timer.c#L1504]

(5) schedule_timeout_uninterruptible() 함수

__set_current_state()함수를 이용하여 프로세스의 상태를 변경합니다.

 

signed long __sched schedule_timeout_uninterruptible(signed long timeout)
{
	__set_current_state(TASK_UNINTERRUPTIBLE);
	return schedule_timeout(timeout);
}

 

 

 

[https://elixir.bootlin.com/linux/v2.6.39.4/source/kernel/timer.c#L1744]

(6) msleep() 함수

(5)번 함수인 schedule_timeout_uninterruptible()를 부릅니다.

 

/**
 * msleep - sleep safely even with waitqueue interruptions
 * @msecs: Time in milliseconds to sleep for
 */
void msleep(unsigned int msecs)
{
	unsigned long timeout = msecs_to_jiffies(msecs) + 1;

	while (timeout)
		timeout = schedule_timeout_uninterruptible(timeout);
}
EXPORT_SYMBOL(msleep);

 

 

 

[https://elixir.bootlin.com/linux/v2.6.39.4/source/kernel/timer.c#L1783]

(7) usleep_range() 함수

__set_current_state() 함수를 이용하여 프로세스의 상태를 변경합니다.

 

/**
 * usleep_range - Drop in replacement for udelay where wakeup is flexible
 * @min: Minimum time in usecs to sleep
 * @max: Maximum time in usecs to sleep
 */
void usleep_range(unsigned long min, unsigned long max)
{
	__set_current_state(TASK_UNINTERRUPTIBLE);
	do_usleep_range(min, max);
}
EXPORT_SYMBOL(usleep_range);