Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이] 커널 타이머 - timer_after/timer_before 함수 [라즈베리파이] 타이머관리

리눅스 커널 중요 시스템은 물론 여러 디바이스 드라이버에서 time_after()와 time_before() 함수를 써서 실행 시간과 타이밍을 제어합니다.

보통 함수 처리 시간과 실행 시간의 데드라인을 점검합니다. 그러니 이 함수들이 어떤 역할을 하는지 잘 알아둘 필요가 있습니다. 

먼저 time_after()와 time_before() 함수 구현부를 보겠습니다.
[include/linux/jiffies.h]
1 #define time_after(a,b) \
2 (typecheck(unsigned long, a) && \
3  typecheck(unsigned long, b) && \
4  ((long)((b) - (a)) < 0))
5 #define time_before(a,b) time_after(b,a)

time_before() 함수 구현부를 보면 인자 값을 서로 바꾼 다음 time_after() 함수를 호출하므로 time_after() 함수를 먼저 분석합시다.

1번 줄 코드를 보면 첫 번째 인자로 a, 두 번째로 b를 받습니다.
2~3번 줄에서 입력 인자가 정수 타입인지 점검하고, 4번 줄에서 b에서 a를 빼고 난 결과를 리턴합니다.

만약 a가 1000이고 b가 1100이면 어떤 결괏값일까요?
100 = (1100 - 1000) = (b - a)

결괏값이 100이므로 0을 리턴합니다.  a가 b보다 크면 1, 아니면 0을 리턴하는 것입니다.
정리하면, a가 b보다 큰지 물어보는 함수입니다.

반대로 time_before() 함수는 a가 b보다 작은지 알려주는 함수입니다. a가 b보다 크면 1, 아니면 0을 리턴합니다.

이제 time_after(a, b)와 time_before(a,b) 함수에 전달하는 인자들의 의미를 알아봅시다.
a에는 현재 시각 정보를 b에는 비교하려는 시간 정보를 입력합니다. 둘다 jiffies 기준값입니다. 

위 함수를 구현부를 분석하는 것보다 리눅스 커널 코드에서 이 함수들을 어떻게 쓰는지 알아보는게 이해가 더 빠르니 관련 코드를 열어 보겠습니다. 다음은 워크큐 와치독 점검 루틴인 wq_watchdog_timer_fn() 함수입니다.
[kernel/workqueue.c]
1 static void wq_watchdog_timer_fn(unsigned long data)
2 {
...
3 if (time_after(jiffies, ts + thresh)) {
4 lockup_detected = true;
5 pr_emerg("BUG: workqueue lockup - pool");
6 pr_cont_pool_info(pool);
7 pr_cont(" stuck for %us!\n",
8 jiffies_to_msecs(jiffies - pool_ts) / 1000);
9 }

이 함수의 역할은 각 per-cpu별로 구동하는 워커풀에서 타임스탬프 정보를 확인해서 워크를 큐 했는데 정해진 시간 내에 해당 워크를 실행했는지 점검합니다.

3번째 줄을 보면 jiffies와 (ts+thread)를 비교해서 jiffies가 더 크면 time_after() 함수는 1을 리턴합니다. 워크큐 락업이라고 판단하고 if 문 내에서 에러 메시지를 출력하는 코드를 실행합니다. 현재 시각이 지정한 마감 시각보다 큰지 점검하는 동작입니다.

이번에는 Soft IRQ 핵심 함수인 __do_softirq()를 확인해 봅시다.
[kernel/softirq.c]
1 asmlinkage __visible void __softirq_entry __do_softirq(void)
2 {
3 unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
...
4 pending = local_softirq_pending();
5 if (pending) {
6 if (time_before(jiffies, end) && !need_resched() &&
7     --max_restart)
8 goto restart;
9
10 wakeup_softirqd();
11 }
12
13 #define MAX_SOFTIRQ_TIME  msecs_to_jiffies(2)

위 코드는 __do_softirq() 함수 실행 시간이 2ms를 넘었는지 점검합니다. 이전에 msec_to_jiffies(m) 함수는 ((m + 9) / 10) 공식으로 밀리초를 HZ단위로 변환한다고 배웠습니다. 배운 내용을 바로 활용하면 수식의 결과는 (2 + 9)/10 = 1입니다. 3번 줄 코드가 실행할 때 jiffies가 100이면 end 지역 변수는 101입니다.  

3번 코드가 실행될 시점 jiffies에 msecs_to_jiffies(2) 함수 결괏값을 더해 end라는 지역 변수에 저장합니다. 이후 Soft IRQ 서비스 핸들러를 실행한 다음 4번 줄 코드를 실행합니다. 참고로 3~4줄 코드 사이에 while 문 전체를 생략했으니 __do_softirq() 코드 전체를 한번 열어서 읽어 주세요.

이때 jiffies 값이 end보다 작은지 점검합니다. 다른 조건도 있지만 timer_before() 함수 조건만 보겠습니다. 현재 시각인 jiffies가 end보다 작으면 if 문을 실행해서 restart 레이브를 실행하고, 아니면 wakeup_softirqd() 함수를 호출해서 ksoftirqd 쓰레드를 깨웁니다.

리눅스 커널 코드에 time_after()와 time_before() 함수는 모래알같이 자주 볼 수 있습니다.
함수 원리를 제대로 익혀 관련 코드를 보면 바로 이해하도록 평소 준비할 필요가 있습니다.

# Reference (커널 타이머관리)

BR,
Guillermo Austin Kim, 08/01/2018




핑백

덧글

댓글 입력 영역