Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이] Process - 프로세스 상태 [라즈베리파이] 커널 프로세스

이름으로 알 수 있듯이, state 필드는 프로세스가 어떤 동작 중인지 알려줍니다. 이 필드는 플래그의 배열이고 각 플래그는 프로세스 상태를 표현합니다.. 현재 리눅스 버전에서는 이 상태들이 상호 배타적이고, state의 플래그 하나만 설정하므로 나머지 플래그들은 Clear합니다. 프로세스 상태는 아래와 같습니다. 

TASK_RUNNING
#define TASK_RUNNING 0x0000

프로세스가 CPU에서 실행중이거나 실행되려고 기다리는 중입니다. 실제 CPU에서 실행 중인 프로세스는 struct runqueues.curr란 멤버에 등록되어 있습니다.

TASK_INTERRUPTIBLE 
#define TASK_INTERRUPTIBLE 0x0001

프로세스는 특정 조건이 true가 될 때까지 잠들어 있는 중입니다. 인터럽트가 발생하거나, 프로세스가 기다리고 있는 시스템 리소스가 확보되거나, 시그널이 전달되면 프로세스를 깨울 수 있습니다. (즉, 다시 TASK_RUNNING 상태로 돌려놓습니다.)

TASK_UNINTERRUPTIBLE
#define TASK_UNINTERRUPTIBLE 0x0002

TASK_INTERRUPTIBLE 과 같으나, 시그널이 전달될 때 상태가 바뀌지 않습니다. 프로세스가 인터럽트 발생 시 반응하지 않고 특정 이벤트가 발생할 때까지 기다려야 하는 특정 조건에서 설정합니다.
프로세스가 뮤텍스를 확보하지 못해 휴면에 들어갈 때 TASK_UNINTERRUPTIBLE로 변경합니다.

가장 큰 예는 프로세스가 뮤텍스를 얻지 못했을 때 자신을 TASK_UNINTERRUPTIBLE 상태로 변경하고 휴면에 진입합니다. 해당 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.49/source/kernel/locking/mutex.c#L1129]
static noinline void __sched
__mutex_lock_slowpath(struct mutex *lock)
{
__mutex_lock(lock, TASK_UNINTERRUPTIBLE, 0, NULL, _RET_IP_);
}

위와 코드가 실행될 때 해당 콜 스택은 다음과 같습니다.
kworker/u16:3-250   [003] ...2 13594.312403: schedule_preempt_disabled+0x8/0x28 <-__mutex_lock_common+0x520/0xb24
kworker/u16:3-250   [003] ...2 13594.312409: <stack trace>
 => __mutex_lock+0x40/0x50
 => __mutex_lock_slowpath+0x28/0x34
 => mutex_lock+0x54/0x60
 => clk_prepare_lock+0x3c/0x90
 => clk_core_get_rate+0x18/0x50
 => clk_get_rate+0x20/0x34
 => dev_get_cur_freq+0x24/0x54
 => update_devfreq+0xa4/0x1a8
 => devfreq_monitor+0x30/0x8c
 => process_one_work+0x1b0/0x3c4
 => worker_thread+0x20c/0x33c
 => kthread+0x124/0x134
 => ret_from_fork+0x10/0x18

또한 프로세스가 장치 파일을 열고 해당 장치 드라이버가 해당 하드웨어 장치를 프로빙할 때 쓰입니다. 
장치 드라이버는 프로빙이 완료될 때까지 인터럽트를 받으면 안되고, 만약 인터럽트를 받게 되면 하드웨어 장치는 예측불가한 상태에 놓이게 될 수 있기 때문입니다. 

TASK_STOPPED
#define __TASK_STOPPED 0x0004

프로세스 동작이 완료됐습니다. SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU 시그널을 받으면 이 상태로 들어갑니다.

TASK_TRACED 
#define __TASK_TRACED 0x0008

디버거에 의해 프로세스 실행이 멈춘 상태입니다. 프로세스가 또 다른 프로세스에 의해 모니터될 때, 프로세스가 TASK_TRACED 상태에 놓일 수 있습니다. 

다음 두 상태는 state 필드와 exit_state(tsk->exit_state) 필드 모두에 저장될 수 있습니다. 필드 이름으로도 알 수 있듯이 프로세스의 실행이 종료되면 이 상태로 들어갑니다. 


EXIT_ZOMBIE
#define EXIT_ZOMBIE 0x0020
프로세스 실행이 종료되었지만, 부모 프로세스가 아직 wait4() 또는 waitpid() 시스템 콜을 호출하여 종료한 자식 프로세스에 대한 정보를 받아 처리를 안 한 상태를 생각해 봅시다.
부모 프로세스가 죽은 자식 프로세스의 프로세스 디스크립터를 필요로할 수 있어서 커널은 wait() 류의 시스템 콜이 호출되기 전에는 프로세스 디스크립터를 없애지 않습니다.
이런 프로세스 상태가  EXIT_ZOMBIE입니다.

EXIT_DEAD
#define EXIT_DEAD 0x0010

부모 프로세스가 wait4() 또는 waitpid() 시스템 콜을 호출하였기 때문에 시스템이 프로세스를 삭제하는 중입니다. EXIT_ZOMBIE에서 EXIT_DEAD로 변경되면 프로세스에 wait() 류의 시스템 콜을 실행중인 프로세스로 인한 레이스 컨디션을 피할 수 있습니다.

state 필드의 값은 할당 문으로 설정합니다. 예를 들면 다음과 같습니다.
p->state = TASK_RUNNING;

커널에서는 _set_current_state와 set_current_state 매크로를 이용합니다.
#define __set_current_state(state_value) do { current->state = (state_value); } while (0)
#define set_current_state(state_value)  smp_store_mb(current->state, (state_value))

current라는 현재 실행 중인 프로세스 태스크 디스크립터에 접근하는 매크로를 써서 struct task_struct->state 에 프로세스 상태를 저장합니다.

pt3_fetch_thread() 함수의 24번째 줄 코드를 봅시다. 
1 static int pt3_fetch_thread(void *data)
2 {
3 struct pt3_adapter *adap = data;
4 ktime_t delay;
5 bool was_frozen;
6
7 #define PT3_INITIAL_BUF_DROPS 4
8 #define PT3_FETCH_DELAY 10
9 #define PT3_FETCH_DELAY_DELTA 2
10
11 pt3_init_dmabuf(adap);
12 adap->num_discard = PT3_INITIAL_BUF_DROPS;
13
14 dev_dbg(adap->dvb_adap.device, "PT3: [%s] started\n",
15 adap->thread->comm);
16 set_freezable();
17 while (!kthread_freezable_should_stop(&was_frozen)) {
18 if (was_frozen)
19 adap->num_discard = PT3_INITIAL_BUF_DROPS;
20
21 pt3_proc_dma(adap);
22
23 delay = PT3_FETCH_DELAY * NSEC_PER_MSEC;
24 set_current_state(TASK_UNINTERRUPTIBLE);  //<<--

이들 매크로는 특정 프로세스와 현재 실행중인 프로세스의 상태를 설정합니다. 특히나 이들 매크로는 컴파일러나 CPU로 인해 다른 명령어와 할당 연산이 섞이지 않도록 보장해줍니다. 명령어 순서가 섞이면 레이스 컨디션이 발생으로 시스템은 오동작 할 수 있습니다.


덧글

댓글 입력 영역