Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[리눅스커널] 시간관리: 동적 타이머 등록 과정 주요 함수(add_timer/mod_timer/__mod_timer) 분석 7. Kernel Timer Management

동적 타이머를 등록하려면 다음 함수를 호출해야 합니다.
add_timer()  
add_timer_on()  
mod_timer()  

보통 add_timer() 함수를 써서 동적 타이머를 등록하며 동적 타이머 만료 시간을 다시 설정한 후 동적 타이머를 등록하려면 mod_timer() 함수를 호출합니다. 

이번 소절에서는 add_timer() 함수와 mod_timer() 함수 코드를 분석하면서 동적 타이머 등록 과정을 알아보겠습니다.

동적 타이머 실행 예제 코드 분석하기
add_timer()와 mod_timer() 함수를 써서 동적 타이머를 등록하는 예제 코드를 소개하겠습니다. 

먼저 add_timer() 함수를 써서 동적 타이머를 등록하는 예제 코드를 볼까요?  
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/mmc/host/vub300.c]
01 static int vub300_probe(struct usb_interface *interface,
02 const struct usb_device_id *id)
03 {
...
04 timer_setup(&vub300->sg_transfer_timer, vub300_sg_timed_out, 0);
05 kref_get(&vub300->kref);
06 timer_setup(&vub300->inactivity_timer,
07     vub300_inactivity_timer_expired, 0);
08 vub300->inactivity_timer.expires = jiffies + HZ;
09 add_timer(&vub300->inactivity_timer);

4번째 줄 코드부터 봅시다. 
04 timer_setup(&vub300->sg_transfer_timer, vub300_sg_timed_out, 0);

timer_setup() 함수를 호출해 동적 타이머를 초기화 합니다.
동적 타이머 자료구조: &vub300->sg_transfer_timer
동적 타이머 핸들러: vub300_sg_timed_out() 

만약 이 코드가 CPU2에서 실행 중이면 &vub300->sg_transfer_timer.flags는 2가 됩니다.

다음 08번째 줄을 보겠습니다. 
08 vub300->inactivity_timer.expires = jiffies + HZ;

동적 타이머가 만료할 시각을 설정합니다. 

    현재 시각 정보인 jiffies에 HZ를 더하니 1초 후에 동적 타이머가 만료하는 목적의 
     코드입니다.  

이번에 라즈비안에서 동적 타이머를 등록하는 예제 코드를 봅시다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/mmc/host/bcm2835-sdhost.c]
1 bool bcm2835_sdhost_send_command(struct bcm2835_host *host,
2  struct mmc_command *cmd)
3{
...
4 timeout = jiffies;
5 if (!cmd->data && cmd->busy_timeout > 9000)
6 timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
7 else
8 timeout += 10 * HZ;
9 mod_timer(&host->timer, timeout);

코드 분석에 앞서 jiffies와 timeout 변수 단위는 HZ라는 점을 기억합시다.

4 번째 줄 코드를 먼저 봅시다.
4 timeout = jiffies;

timeout 변수에 현재 시각 정보인 jiffies를 저장합니다.

5~8 번째 줄은 timeout 변수에 시각 정보를 더합니다. timeout 변수는 타이머 만료 시각을 나타냅니다.
5 if (!cmd->data && cmd->busy_timeout > 9000)
6 timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
7 else
8 timeout += 10 * HZ;

만약 8번째 줄 코드를 실행하면 HZ에 10을 곱한 값을 timeout 변수에 더합니다.  

    현재 시각 기준으로 10초 후 동적 타이머를 처리하려는 목적입니다. 

9번째 줄 코드는 mod_timer() 함수를 써서 동적 타이머를 등록합니다.
9 mod_timer(&host->timer, timeout);

mod_timer() 함수에 전달하는 인자는 다음과 같습니다.
&host->timer: 동적 타이머 struct timer_list 구조체 주소
timeout: 동적 타이머 만료 시각

그런데 동적 타이머 핸들러와 동적 타이머 속성 정보는 어느 코드에 있을까요? &host->timer 타이머 속성은 bcm2835_sdhost_add_host() 함수에서 확인할 수 있습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/mmc/host/bcm2835-sdhost.c]
1 int bcm2835_sdhost_add_host(struct bcm2835_host *host)
2 {
3 struct mmc_host *mmc;
...
4 INIT_WORK(&host->cmd_wait_wq, bcm2835_sdhost_cmd_wait_work);
5
6 timer_setup(&host->timer, bcm2835_sdhost_timeout, 0);

&host->timer 타이머는 이미 bcm2835_sdhost_add_host() 함수에서 다음 정보로 초기화를 했습니다. 
동적 타이머 핸들러 함수: bcm2835_sdhost_timeout()
timer_list 구조체 주소: &host->timer 

정리하면 timeout 변수가 지정한 동적 타이머 만료 시간에 bcm2835_sdhost_timeout() 타이머 핸들러 함수가 호출합니다.

    그러면 add_timer()와 mod_timer() 함수의 차이점은 뭘까요? 

add_timer() 함수는 동적 타이머 전체 속성을 설정하고 호출해야 합니다. 대신 mod_timer() 함수는 동적 타이머 만료 시각을 바꾼 다음 동적 타이머를 등록합니다. 두 함수의 동작은 다음과 같이 정리할 수 있습니다.

add_timer() 함수
동적 타이머 세부 속성 설정
동적 타이머 등록

mod_timer() 함수
동적 타이머 만료 시각 설정
동적 타이머 등록

add_timer()와 mod_timer() 함수 분석하기
동적 타이머를 등록하려면 add_timer() 함수와 mode_timer() 함수를 호출해야 합니다. 두 함수의 구현부를 먼저 소개하고 세부 소스 코드 분석을 하겠습니다. 
 
add_timer() 함수 선언부를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/timer.h]
extern void add_timer(struct timer_list *timer);

함수 선언부가 void이니 함수 실행 결과를 반환하지 않습니다. 함수인자로 struct timer_list 구조체 주소를 전달해야 합니다.

add_timer() 함수 구현부 코드를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
01 void add_timer(struct timer_list *timer)
02 {
03 BUG_ON(timer_pending(timer));
04 mod_timer(timer, timer->expires);
05 }

add_timer() 함수 구현부 코드는 간단합니다. 특별한 일을 하지 않고 03 번째 줄에서 예외 조건 점검 후 mod_timer() 함수를 호출합니다.

03 번째 줄 코드를 보면 timer_pending(timer); 함수를 호출해 true를 반환하면 BUG_ON() 함수를 호출합니다. BUG_ON() 함수는 매크로 타입으로 커널 패닉을 유발하는 기능입니다. 

timer_pending() 함수는 동적 타이머를 이미 등록했으면 true를 반환합니다. 

    동적 타이머를 이미 등록했는데 다시 등록하면 BUG_ON() 매크로 함수를 실행 해 커널 
   패닉을 일으키는 코드입니다.

다음 04 번째 줄 코드입니다.
04 mod_timer(timer, timer->expires);

다음 인자를 mod_timer() 함수로 전달하며 호출합니다.
timer: struct timer_list 구조체 주소
timer->expires: 동적 타이머 만료 시각(HZ 단위)

mod_timer() 함수 분석하기
먼저 mod_timer() 함수 선언부를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/timer.h]
extern int mod_timer(struct timer_list *timer, unsigned long expires);

반환값
동적 타이머 만료 시각을 설정했으면 0, 아닌 경우 1를 반환합니다.

인자
struct timer_list *timer;
동적 타이머를 표현하는 struct timer_list 구조체 주소입니다.

unsigned long expires;
동적 타이머 만료 시각(HZ단위)을 저장합니다.

mod_timer() 함수 선언부를 소개했으니 함수 코드를 소개할 차례입니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
01 int mod_timer(struct timer_list *timer, unsigned long expires)
02 {
03  return __mod_timer(timer, expires, false);
04 }

mod_timer() 함수는 __mod_timer() 함수를 호출합니다. 


__mod_timer() 함수 앞에 __기호가 보입니다. 리눅스 커널 코딩 룰에 따르면 __가 붙은 함수는 커널 함수 내에서만 호출할 수 있고 다른 커널 서브 시스템에서 호출하지 못합니다. 

리눅스 커널에서 가장 유명한 schedule() 함수를 예를 들어 봅시다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/sched/core.c]
01 asmlinkage __visible void __sched schedule(void)
02 {
03 struct task_struct *tsk = current;
04
05 sched_submit_work(tsk);
06 do {
07 preempt_disable();
08 __schedule(false);
09 sched_preempt_enable_no_resched();
10 } while (need_resched());
11 }

schedule() 함수 08 번째 줄에서 __schedule() 함수를 호출합니다. __schedule() 함수는 스케줄러 서브 시스템 내부에서만 호출할 수 있습니다. 


__mod_timer() 함수 분석하기 
mod_timer() 함수에서 호출하는 __mod_timer() 는 동적 타이머를 등록하는 커널 내부 함수입니다. __mod_timer() 함수에 전달하는 인자를 알아보고 코드를 분석하겠습니다.

먼저 __mod_timer() 함수 선언부를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
static inline int
__mod_timer(struct timer_list *timer, unsigned long expires, unsigned int options);

이 함수에 전달하는 인자를 알아볼까요? 
struct timer_list *timer;
struct timer_list 구조체 주소 

unsigned long expires;
동적 타이머 만료 시각 

bool pending_only;
__mode_timer에서 false로 지정하므로 0임

함수 인자를 살펴봤으니 __mod_timer() 함수를 분석하겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
01 static inline int
02 __mod_timer(struct timer_list *timer, unsigned long expires, unsigned int options)
03 {
04 struct timer_base *base, *new_base;
05 unsigned int idx = UINT_MAX;
06 unsigned long clk = 0, flags;
07 int ret = 0;
08
09 BUG_ON(!timer->function);
11
12 if (timer_pending(timer)) {
13 long diff = timer->expires - expires;
14
15 if (!diff)
16 return 1;
17 if (options & MOD_TIMER_REDUCE && diff <= 0)
18 return 1;
19 base = lock_timer_base(timer, &flags);
20 forward_timer_base(base);
21
22 if (timer_pending(timer) && (options & MOD_TIMER_REDUCE) &&
23     time_before_eq(timer->expires, expires)) {
24 ret = 1;
25 goto out_unlock;
26 }
27
28 clk = base->clk;
29 idx = calc_wheel_index(expires, clk);
...
30 } else {
31 base = lock_timer_base(timer, &flags);
32 forward_timer_base(base);
33 }
34
35 ret = detach_if_pending(timer, base, false);
...
36 debug_activate(timer, expires);
37
38 timer->expires = expires;
39
40 if (idx != UINT_MAX && clk == base->clk) {
41 enqueue_timer(base, timer, idx);
42 trigger_dyntick_cpu(base, timer);
43 } else {
44 internal_add_timer(base, timer);
45 }
46
47 out_unlock:
48 raw_spin_unlock_irqrestore(&base->lock, flags);
49
50 return ret;
51 }

이제 코드 분석으로 들어갑니다. 먼저 9번째 줄 코드를 보겠습니다.
9 BUG_ON(!timer->function);

struct timer_list 구조체 function 필드가 NULL이면 BUG_ON() 함수를 호출합니다. 

    동적 타이머 핸들러가 NULL이면 BUG_ON() 함수를 실행해 커널 패닉을 유발합니다. 

이 코드로 디바이스 드라이버에서 동적 타이머 핸들러를 설정한 후 동적 타이머를 등록해야 한다는 점을 알 수 있습니다.

이어서 12~18 번째 줄 코드를 보겠습니다.
12 if (timer_pending(timer)) {
13 long diff = timer->expires - expires;
14
15 if (!diff)
16 return 1;
17 if (options & MOD_TIMER_REDUCE && diff <= 0)
18 return 1;

timer_pending() 함수를 호출해 true를 반환하면 동적 타이머를 이미 등록한 것입니다. 
이 조건을 만족하면 13~18 번째 줄 코드를 실행해 예외 처리를 수행합니다.

다음 13~16번째 줄 코드를 보겠습니다.
13 long diff = timer->expires - expires;
14
15 if (!diff)
16 return 1;

이미 등록한 동적 타이머 만료 시각과 새롭게 등록하는 만료 시각을 뺍니다. 만약 두 값이 같으면 diff는 0일 것입니다. 이 조건에서 (!0 = 1)이니 16번째 줄 코드를 실행합니다. 1를 반환하면서 함수 실행을 종료합니다.

다음 19번 째 줄을 보겠습니다.
19 base = lock_timer_base(timer, &flags);

19번 째 줄 코드는 lock_timer_base() 함수를 호출해 struct timer_base 구조체 포인터 타입 변수인 base에 반환값을 저장합니다. lock_timer_base() 함수를 조금 후 분석합니다.

20 번째 줄 코드를 봅시다. 
20 forward_timer_base(base);

forward_timer_base() 함수를 호출해서 struct timer_base 구조체 clk 필드를 현재 시각인 jiffies 값으로 저장합니다. struct timer_base 구조체 clk 필드는 커널 시스템 타이머 실행 여부를 판단하는 중요한 정보를 저장합니다.

17번째 줄 struct timer_base 구조체 clk 필드를 clk로 저장합니다.
28 clk = base->clk;

29번째 줄은 calc_wheel_index() 함수를 호출해서 커널 타이머 벡터 인덱스를 읽습니다. 
29 idx = calc_wheel_index(expires, clk);

커널 타이머 해시 벡터는 동적 시간을 만료 시각 기준으로 동적 타이머 64(1 << 6) 개를 8개씩 묶어서 관리합니다. 이 커널 타이머 해시 벡터 인덱스의 범위는 0부터 512입니다.

이어서 31~32 번째 줄 코드입니다.
12 if (timer_pending(timer)) {
...
30 } else {
31 base = lock_timer_base(timer, &flags);
32 forward_timer_base(base);
33 }

먼저 31~32번째 줄 코드가 실행하는 조건을 확인하겠습니다. 31~32번째 코드는 12번째 줄 코드에서 timer_pending()이란 함수가 0을 반환했을 때 실행하는 else문입니다. 동적 타이머를 처음 등록하거나 동적 타이머가 만료된 후 다시 등록할 때 31~32 번째 줄 코드를 실행합니다. 

    일반적인 상황에서 31~32번째 줄 코드를 실행합니다.

이제 31번째 줄 코드를 보겠습니다. 동적 타이머를 초기화했을 때 CPU 번호가 저장된 struct timer_list 구조체 flags 필드 변수 기준으로 per-cpu 타입 timer_base 전역 변수 주소를 읽어 base 지역 변수로 저장합니다. 

32번째 줄 코드는 forward_timer_base() 함수를 호출해 struct timer_base 구조체 clk 필드를 현재 시각인 jiffies로 저장합니다.

다음 35번째 줄 코드를 봅시다.
35 ret = detach_if_pending(timer, base, false);

detach_if_pending() 함수를 실행해서 이미 timer_base 타이머 해시 벡터인 timer_base.vector[512]에 지정한 링크를 끊습니다.   

__mod_timer() 함수 코드 분석으로 다음 내용을 알게 됐습니다.
반복해서 동적 타이머를 등록하면 1을 반환하며 실행 종료 
동적 타이머는 timer_base 타이머 해시 벡터에 등록함

이어서 동적 타이머 등록 과정에서 호출하는 커널 타이머 내부 함수를 소개합니다.


#커널 시간관리 목차
커널 타이머 관리 주요 개념 소개
jiffies란
커널 타이머 제어
동적 타이머 초기화
동적 타이머 등록하기
동적 타이머는 누가 언제 실행하나?
라즈베리파이 커널 타이머 실습 및 로그 분석

"혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답글 올려드리겠습니다!"



핑백

덧글

댓글 입력 영역