Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

110187
803
94439


[리눅스커널] 시간관리: 동적 타이머 등록 과정 세부 커널 타이머 함수 분석 7장. 타이머관리

이번 소절에서는 __mod_timer() 함수에서 호출한 커널 타이머 함수를 분석합니다.
timer_pending()
lock_timer_base()
forward_timer_base()
enqueue_timer()

처음 리눅스 커널을 접하는 분들에겐 분석하기 어려운 함수들입니다. __mod_timer() 함수 이외 다른 커널 타이머 함수에서 호출하니 사용 용도라도 익혀둡시다. 

timer_pending() 함수 분석
timer_pending() 함수는 동적 타이머가 이미 등록된 상태인지를 알려줍니다.

함수 선언부는 다음과 같습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/timer.h]
static inline int timer_pending(const struct timer_list * timer);

인자 타입은 동적 타이머를 핸들링하는 struct timer_list 구조체입니다.
동적 타이머가 이미 등록됐으면 1을 반환합니다.


함수 선언부로 인라인 함수란 사실을 알 수 있습니다. 
인라인 함수는 매우 자주 호출되거나 함수 구현부가 간단한 경우가 많습니다.


timer_pending() 함수 구현부 코드를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/timer.h]
01 static inline int timer_pending(const struct timer_list * timer)
02 {
03 return timer->entry.pprev != NULL;
04 }

struct timer_list 구조체 entry 필드는 struct hlist_node 타입으로 해시 리스트입니다.  
entry.pprev가 NULL이 아니면 1을 반환합니다. 

커널 타이머 관점으로 이 코드의 의미는 이미 동적 타이머를 등록했을 때 1을 반환합니다. 
동적 타이머를 등록하면 timer->entry.pprev 포인터는 per-cpu타입 timer_base 변수의 벡터 해시 테이블 주소를 가르킵니다.

레이스 컨디션으로 동적 타이머를 중복해 등록할 때 예외 처리 코드에서 자주 호출하는 함수입니다.

lock_timer_base() 함수 분석  
lock_timer_base() 함수를 호출하면 다음 순서로 함수를 호출해 per-cpu 오프셋을 적용한 timer_base 주소를 읽습니다.
get_timer_base() 함수
get_timer_cpu_base() 함수
 
처음 동적 타이머를 초기화할 때 실행한 CPU 번호 기준으로 timer_bases 전역 변수를 percpu 오프셋을 적용해 읽습니다. 동적 타이머를 초기화할 때 CPU 번호는 struct timer_list 구조체 flags 필드에 저장돼 있습니다.

lock_timer_base() 함수 코드를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
01 static struct timer_base *lock_timer_base(struct timer_list *timer,
02   unsigned long *flags)
03 __acquires(timer->base->lock)
04{
05 for (;;) {
...
06 if (!(tf & TIMER_MIGRATING)) {
07 base = get_timer_base(tf);

06 번째 줄은 현재 동적 타이머를 마이그레이팅하는 상태가 아니면 07 번째 줄 코드를 실행합니다. 대부분 상황에서 07 번째 줄 코드를 실행합니다.


마이그레이션은 percpu 타입 변수를 관리할 때 적용하는 기법입니다. 특정 CPU 번호 percpu 변수에 일이 몰리면 다른 CPU에서 일을 분산시키는 기법입니다. 


get_timer_base() 함수 코드를 보겠습니다.
01 static inline struct timer_base *get_timer_base(u32 tflags)
02 {
03 return get_timer_cpu_base(tflags, tflags & TIMER_CPUMASK);
04 }

04번째 줄 코드와 같이 함수에 전달된 인자 tflags와 tflags를 TIMER_CPUMASK 플래그로 AND 비트 연산한 결과를 인자로 get_timer_cpu_base() 함수를 호출합니다.

이어서 get_timer_cpu_base() 함수를 보겠습니다.
01 static inline struct timer_base *get_timer_cpu_base(u32 tflags, u32 cpu)
02 {
03 struct timer_base *base = per_cpu_ptr(&timer_bases[BASE_STD], cpu);
...
04 return base;
05 }

커널 타이머 베이스 변수인 timer_bases에 두 번째 cpu인자 오프셋을 적용한 주소를 반환합니다.

forward_timer_base() 함수 분석하기 
forward_timer_base() 함수는 타이머 베이스의 clk 필드를 현재 시각으로 바꿔줍니다.
01 static inline void forward_timer_base(struct timer_base *base)
02 {
03 #ifdef CONFIG_NO_HZ_COMMON
04 unsigned long jnow;
...
05 jnow = READ_ONCE(jiffies);
...
06 if (time_after(base->next_expiry, jnow))
07 base->clk = jnow;
08 else
09 base->clk = base->next_expiry;
10 #endif
11 } 
 
위 함수의 핵심 동작은 다음과 같습니다.   
struct timer_base 구조체 clk 필드에 현재 시각인 jiffies 값으로 저장합니다. 

여기서 struct timer_base 구조체 clk 필드는 커널 시스템 타이머 실행 여부를 판단하는 중요한 정보입니다.

05 번째 줄 코드를 보겠습니다.
05 jnow = READ_ONCE(jiffies);

jiffies 를 읽어 jnow 지역변수에 저장합니다.

이어서 06~09 번째 줄 코드입니다.
06 if (time_after(base->next_expiry, jnow))
07 base->clk = jnow;
08 else
09 base->clk = base->next_expiry;

base->next_expiry 가 jnow보다 크면 07번째 줄과 같이 base->clk에 jnow를 저장합니다.
반대 경우 09번째 줄과 같이 base->clk에 base->next_expiry를 저장합니다.

enqueue_timer() 함수
enqueue_timer() 함수는 동적 타이머를 per-cpu 타입 timer_bases 변수 해시 벡터 테이블에 등록하는 역할입니다.

enqueue_timer() 함수 코드를 보겠습니다.  
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
1 static void enqueue_timer(struct timer_base *base, struct timer_list *timer,
2   unsigned int idx)
3 {
4 hlist_add_head(&timer->entry, base->vectors + idx);
5 __set_bit(idx, base->pending_map);
6 timer_set_idx(timer, idx);
7}

먼저 함수에 전달하는 인자를 점검해봅시다.
struct timer_base *base: per-cpu 타입 timer_bases 전역 변수에서 현재 구동 중인 CPU에 해당하는 오프셋을 적용한 주소를 저장하고 있습니다.
struct timer_list *timer: 등록하려는 동적 타이머 정보입니다.

4번째 줄 코드는 base->vectors 필드인 타이머 해시 벡터 테이블 시작 주소에 타이머 해시 벡터 인덱스를 더합니다. struct timer_base 구조체 필드인 vectors 시작 주소 기준으로 idx만큼 위치한 주소에 timer->entry 주소를 저장하는 것입니다. 또한 struct timer_base 구조체 pending_map 필드에 pending 비트를 설정합니다.

여기까지 동적 타이머를 설정하면 처리하는 데이터와 관련 자료구조를 살펴봤습니다. 다음 절에서 이어서 동적 타이머 만료 시간에 동적 타이머를 처리하는 동작을 살펴보겠습니다.

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

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



핑백

덧글

댓글 입력 영역