Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

8200
629
98815


[리눅스커널] 시간관리: 동적 타이머 초기화 7장. 타이머관리

동적 타이머를 쓰려면 먼저 동적 타이머를 초기화해야 합니다. 다음 내용을 살펴보면서 동적 타이머 초기화 과정을 소개합니다.
timer_setup() 함수 소개
동적 타이머 초기화 예제 코드 분석

timer_setup() 함수 코드 분석하기
동적 타이머를 초기화하려면 timer_setup() 함수를 호출해야 합니다.

먼저 timer_setup() 함수 선언부를 소개합니다.
void timer_setup(struct timer_list *timer, void *func, unsigned int flags);

함수에 전달되는 인자 속성은 다음과 같습니다.
struct timer_list *timer: 동적 타이머 핸들
void *func: 동적 타이머 핸들러 함수
unsigned int flags: 동적 타이머 플래그 



커널 4.14 버전까지 동적 타이머를 초기화하려면 setup_timer() 함수나 init_timer() 함수를 써야 했습니다. 커널 4.15 버전부터 timer_setup() 함수를 써야 동적 타이머를 초기화할 수 있습니다. 


timer_setup() 함수 선언부를 소개했으니 구현부 코드를 분석할 차례입니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/timer.h]
01 #define timer_setup(timer, callback, flags) \
02 __init_timer((timer), (callback), (flags))
03
04 #define __init_timer(_timer, _fn, _flags) \
05 do { \
06 static struct lock_class_key __key; \
07 init_timer_key((_timer), (_fn), (_flags), #_timer, &__key);\
08 } while (0)

02 번째 줄 코드를 보면 timer_setup() 함수는 timer, callback, flags 인자를 채워 그대로 __init_timer() 함수로 치환됩니다. 04 번째 줄 __init_timer() 함수 선언부를 보면 07 번째 줄 코드와 같이 init_timer_key() 함수를 호출합니다.

다음 init_timer_key() 함수 코드를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
void init_timer_key(struct timer_list *timer,
    void (*func)(struct timer_list *), unsigned int flags,
    const char *name, struct lock_class_key *key)
{
debug_init(timer);
do_init_timer(timer, func, flags, name, key);
}

init_timer_key() 함수는 debug_init()와 do_init_timer() 함수를 호출합니다. 먼저 debug_init() 함수부터 살펴 보겠습니다. 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
01 static inline void debug_init(struct timer_list *timer)
02 {
03 debug_timer_init(timer);
04 trace_timer_init(timer);
05 }

debug_init() 함수는 debug_timer_init() 함수와 trace_timer_init() 함수를 호출합니다. 각각 함수 역할을 알아보겠습니다.

3번째 줄 debug_timer_init() 함수는 CONFIG_DEBUG_OBJECTS_TIMERS란 디버그용 컨피그를 설정하면 실행하는 함수입니다. 동적 타이머를 중복해서 초기화를 할 경우 WARN() 함수를 매크로를 실행해서 에러 메시지를 출력하는 동작을 수행합니다. 라즈베리안에서는 기본설정으로 이 컨피그는 꺼져 있습니다.


WARN() 함수는 커널 동작에 오류가 생겼을 때 콜스택과 함께 에러 메시지를 출력합니다.


4번째 줄 trace_timer_init() 함수를 실행하면 ftrace timer_init 이벤트 로그를 출력합니다. 다음 명령어로 ftrace의 timer_init 이벤트를 설정하면 trace_timer_init() 함수가 실행하며 ftrace 로그를 출력합니다. 
“echo 1 > /sys/kernel/debug/tracing/event/timer/timer_init/enable"

위와 같이 timer_init 이벤트를 킨 상태에서 trace_timer_init() 함수를 호출하면 다음과 같은 ftrace 로그를 출력합니다.
rcu_sched-8     [002] ....  5181.011526: timer_init: timer=b9e7bed0

위 로그는 5181.011526 초에 동적 타이머를 초기화했다는 메시지입니다. 동적 타이머를 표현하는 struct timer_list 구조체가 위치한 주소는 b9e7bed0입니다.

    정리하면 debug_init() 함수는 동적 커널 타이머 디버깅 정보를 출력하는 역할입니다.

이번에는 init_timer_key() 함수에서 호출하는 do_init_timer() 함수 코드를 분석하겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
01 static void do_init_timer(struct timer_list *timer,
02   void (*func)(struct timer_list *),
03   unsigned int flags,
04   const char *name, struct lock_class_key *key)
05 {
06 timer->entry.pprev = NULL;
07 timer->function = func;
08 timer->flags = flags | raw_smp_processor_id();
09 lockdep_init_map(&timer->lockdep_map, name, key, 0);
10 }

먼저 7 번째 줄 코드를 보겠습니다. 동적 타이머 핸들러 함수를 timer->function 필드에 저장합니다.

다음 8 번째 줄 코드를 보겠습니다.
입력인자 flags와 현재 실행 중인 CPU 번호를 OR 비트 연산한 결과를 timer->flags 필드에 저장합니다. raw_smp_processor_id() 함수는 현재 실행 중인 코드가 몇 번째 CPU에서 수행 중인지 알려줍니다. 

만약 CPU3번에서 do_init_timer() 함수가 실행했다면 timer->flags는 무슨 값일까요?

    CPU3에서 실행 중이니 0x3이 됩니다.


이렇게 동적 타이머를 초기화하는 흐름을 코드 분석으로 알아봤습니다. 분석한 내용이 잘 이해되시나요? 소스 코드만 분석하면 배운 내용이 머릿속에 잘 남지 않을 수도 있습니다.

리눅스 커널 코드는 함수 흐름으로 개념을 이해할 수도 있지만 정밀히 데이터를 연산하거나 변환하는 코드는 자료구조를 직접 눈으로 확인하는 것이 이해가 빠릅니다. 이해를 돕기 위해 이번에는 ftrace 로 코드로 분석한 동적 타이머를 초기화할 때 자료구조를 확인하겠습니다.

ftrace로 timer_init 이벤트를 키면 다음과 같은 ftrace 로그를 볼 수 있다고 했습니다.
 rcu_sched-8     [002] ....  5181.011526: timer_init: timer=b9e7bed0

위 코드에서 볼드체로 002라고 돼 있는 숫자는 이 코드를 실행하고 있는 CPU 번호를 의미합니다. 또한 rcu_sched는 프로세스 이름이고 8을 pid를 의미합니다. 두 정보를 토대로 위 ftrace 로그는 다음과 같이 해석할 수 있습니다. 

    CPU2에서 “rcu_sched” 이란 프로세스가 구동 중입니다. 

만약 위 ftrace 로그가 실행됐을 때는 timer->flags 필드는 2로 업데이트됩니다. 이 함수는 CPU2에서 구동하고 있기 때문입니다. 


동적 타이머 초기화 예제 코드 분석하기
지금까지 동적 타이머를 초기화하는 과정에서 실행하는 함수를 분석했습니다. 이번에는 동적 타이머를 초기화하는 예제 코드를 보겠습니다. 다음은 라즈비안에서 동적 타이머를 초기화하는 코드 조각입니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/drivers/mmc/host/bcm2835-sdhost.c]
01 int bcm2835_sdhost_add_host(struct bcm2835_host *host)
02 {
03 struct mmc_host *mmc;
...
04 timer_setup(&host->timer, bcm2835_sdhost_timeout, 0);

04 번째 줄 코드를 보겠습니다. 
timer_setup() 함수를 써서 동적 타이머를 초기화합니다. timer_setup() 함수로 3개 인자를 전달합니다. 
host->timer: struct timer_list 구조체 주소
bcm2835_sdhost_timeout: 동적 타이머 핸들러
0: struct timer_list 구조체 flags 플래그 설정 값
 
이번에 struct timer_list 구조체 필드가 어떤 값인지 확인해 볼까요?
 (struct timer_list *) (struct timer_list *)0xb9e7bed0 = 0xB9E7BED0 -> (
    (struct hlist_node) entry = ((struct hlist_node *) next = 0x0, 
    (long unsigned int) expires = 0,
    (void (*)()) function = 0x0,
    (long unsigned int) data = 0,
    (u32) flags = 2)

각각 필드들은 0x0인데 flags 필드만 2입니다. CPU2에서 init_timer_key() 함수를 실행했기 때문입니다.

이번 절에 코드 분석으로 init_timer() 함수를 호출하면 간단히 동적 타이머를 초기화한다는 사실을 알게 됐습니다. 현재 구동 중인 CPU 번호를 계산해서 timer->flags 변수에 저장할 뿐입니다. 여기서 한 가지 의문이 생깁니다.

    그러면 현재 실행 중인 CPU 번호로 timer->flags 변수를 저장하는 이유는 뭘까? 

동적 타이머를 실행할 때 이 값을 바탕으로 struct timer_base 구조체 per-cpu 타입 timer_bases 주소에 접근합니다. 

동적 타이머를 설정한 CPU 번호가 1번이면 per-cpu1 timer_bases를 로딩해서 동적 타이머를 저장합니다. 이 내용은 다음 8.4 절에서 상세히 다룰 예정입니다.

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

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




핑백

덧글

댓글 입력 영역