Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[라즈베리파이] 커널 타이머 - msecs_to_jiffies 함수란 [라즈베리파이] 타이머관리

커널은 타이머를 HZ 단위 상대 시간을 나타내는 jiffies값을 기준으로 관리 합니다. 그리고 커널 타이머 함수는 HZ 단위 시간 정보를 받아 처리합니다. 그래서 커널이 이해할 수 있는 단위로 시간 정보를 변환시켜 줘야 합니다. msecs_to_jiffies() 함수가 이 역할을 수행합니다. 이 함수는 가독성도 높습니다. 함수 이름만 봐도 무슨 뜻인지 알기 쉽습니다.

실제 리눅스 커널에서 msecs_to_jiffies() 함수를 쓰는 코드를 살펴보겠습니다. 

다음은 pstore 파일 시스템 코드 일부 조각입니다.
[fs/pstore/platform.c]
pstore_timer.expires = jiffies +
2 msecs_to_jiffies(pstore_update_ms);
3 add_timer(&pstore_timer);

1번 줄 코드를 보면 pstore_update이란 밀리초 단위 상수를 msecs_to_jiffies() 함수를 써서 HZ(jiffies) 단위로 변환 후 pstore_timer.expires에 저장합니다. 이렇게 동적 타이머 만료 시각을 설정한 후 3번 줄 코드와 같이 add_timer() 함수를 호출해서 동적 타이머를 실행합니다. 

다른 코드를 보겠습니다.
[arch/ia64/kernel/salinfo.c]
1 static int __init
2 salinfo_init(void)
3 {
4 init_timer(&salinfo_timer);
5 salinfo_timer.expires = jiffies + SALINFO_TIMER_DELAY;
6 salinfo_timer.function = &salinfo_timeout;
7 add_timer(&salinfo_timer);

8#define SALINFO_TIMER_DELAY (60*HZ)

5번 줄 코드를 보면 현재 시각인 jiffies에 SALINFO_TIMER_DELAY이란 매크로를 더해서 만료 시각으로 설정합니다. 8번 줄 코드와 같이 SALINFO_TIMER_DELAY는 (60 * HZ) 이므로 60초를 의미합니다. 7번 줄을 보면 이번에도 add_timer() 함수를 호출해서 60초 만료 시각으로 동적 타이머를 실행합니다.

msecs_to_jiffies() 함수는 커널 코드에서 많이 쓰므로 어떤 의미인지 잘 알아 둘 필요가 있습니다. 바로 msecs_to_jiffies() 함수 구현부를 보겠습니다.
1 static __always_inline unsigned long msecs_to_jiffies(const unsigned int m)
2 {
3 if (__builtin_constant_p(m)) {
4 if ((int)m < 0)
5 return MAX_JIFFY_OFFSET;
6 return _msecs_to_jiffies(m);
7 } else {
8 return __msecs_to_jiffies(m);
9 }
10}

3번 줄 코드는 msecs_to_jiffies() 함수 입력 인자인 m 값이 정수인지 점검하는 코드입니다. msecs_to_jiffies() 함수는 인라인 형태 함수입니다. 심볼 없이 함수에 복사되는 코드 블록입니다. 컴파일러는 m이 정수가 아닌 경우 0으로 리턴하고 정수인 경우 1로 리턴합니다.

4번 줄은 m이라는 인자가 0보다 작을 경우 MAX_JIFFY_OFFSET 매크로를 리턴합니다. msecs_to_jiffies() 함수 선언부를 보면 unsigned long 타입 변수를 리턴하므로 MAX_JIFFY_OFFSET 매크로도 unsigned long일 겁니다.

이제 MAX_JIFFY_OFFSET 매크로 선언부를 보겠습니다.
#define MAX_JIFFY_OFFSET ((LONG_MAX >> 1)-1)
#define LONG_MAX ((long)(~0UL>>1))

MAX_JIFFY_OFFSET 매크로는 LONG_MAX 매크로를 치환해서 비트 연산자를 구성합니다.
결국 LONG_MAX 매크로에 선언된 연산자를 MAX_JIFFY_OFFSET에 대입하면 다음과 같은 코드를 볼 수 있습니다.
#define MAX_JIFFY_OFFSET ((((long)(~0UL>>1)) >> 1)-1)

MAX_JIFFY_OFFSET 매크로에 선언된 연산자를 단계별로 풀어볼까요?
((((long)(~0UL>>1)) >> 1)-1)
((((long)(0xFFFFFFFF >> 1)) >> 1)-1)
((((long)(0x3FFFFFFF)) >> 1)-1)
((0x1FFFFFFF)-1)
0x1FFFFFFE (536870910)

단계별로 계산한 연산 결과는 0x1FFFFFFE (536870910)입니다. 라즈베리파이에서는 HZ가 100이니 연산 결과는 5368709초이고 89,478분입니다. 밀리초를 음수로 전달하면 엄청난 HZ 단위 jiffies 값을 리턴합니다.

3~5번 줄은 예외 처리 코드입니다. 제대로 m인자를 밀리 초 상수 단위로 전달하면 6번 코드가 실행합니다.

이번에 6줄 코드를 봅시다. msecs_to_jiffies() 함수에 밀리초 단위 숫자를 인자로 전달하면 _msecs_to_jiffies(m) 함수를 호출합니다.

이제 _msecs_to_jiffies() 함수를 봅시다. _msecs_to_jiffies() 함수는 매크로로 연산을 수행하니 C 코드와 전처리 코드로 함수를 같이 보면 빨리 코드를 읽을 수 있습니다.
static inline unsigned long _msecs_to_jiffies(const unsigned int m)
{
return (m + (MSEC_PER_SEC / HZ) - 1) / (MSEC_PER_SEC / HZ);
}

static inline  unsigned long _msecs_to_jiffies(const unsigned int m)
 {
  return (m + (1000L / 100) - 1) / (1000L / 100);
 }

MSEC_PER_SEC 매크로는 1000L, HZ는 100입니다. 각 CPU 별로 다른 HZ 설정값에 따라 다른 연산을 하도록 구현된 코드입니다. 

_msecs_to_jiffies() 함수는 (m + (1000L / 100) - 1) / (1000L / 100) 연산자 수식임을 알 수 있습니다.

이번에도 복잡해 보이는 연산자 수식을 단계별로 풀어 봅시다.
m + (1000L / 100) – 1)/(1000L / 100);
m + (10) – 1) / 10;
(m + 9) / 10;

위 연산자를 차례로 푸니 밀리 초는 (m + 9)/10 공식으로 HZ 단위로 변환한다는 사실을 알 수 있습니다.

만약 10ms이면 jiffies 값은 어떻게 변환할까요? 다음 공식으로 1이됩니다.
(10 + 9) / 10 = 19 / 10 = 1

이번에는 11ms부터 20ms를 jiffie로 변환한 결과입니다.
밀리초     jiffies
11 2
12 2
13 2
14 2
15 2
16 2
17 2
18 2
19 2
20 2
21 3

HZ가 100이면 -+5ms만큼 총 10ms 오차가 있음을 알 수 있습니다.

HZ 단위 값으로 실행 흐름을 제어하는 코드를 짤 때 10ms 정도 오차가 있음을 염두할 필요가 있습니다. 또한 msecs_to_jiffies() 함수를 보면 위에서 살펴본 바와 같이 (m + 9)/10 이라는 공식을 떠올려 수식을 계산해 봅시다. 바로 코드를 읽을 수 있으니 더 빨리 동작을 이해할 수 있습니다.

이번에 밀리 초를 jiffies(HZ) 단위로 바꾸는 msecs_to_jiffies() 함수를 알아봤습니다. 다음에 HZ단위인 jiffies로 커널 실행 흐름을 제어하는 방법을 알아보겠습니다.

#Reference 시스템 콜


Reference(워크큐)
워크큐(Workqueue) Overview

BR,
Guillermo Austin Kim, 07/30/2018

핑백

덧글

댓글 입력 영역