Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

0112
737
82110


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

커널은 타이머를 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(프로세스 관리)
4.9 프로세스 컨택스트 정보는 어떻게 저장할까?
 4.9.1 컨택스트 소개
 4.9.2 인터럽트 컨택스트 정보 확인하기
 4.9.3 Soft IRQ 컨택스트 정보 확인하기
 4.9.4 선점 스케줄링 여부 정보 저장
4.10 프로세스 디스크립터 접근 매크로 함수
 4.10.1 current_thread_info()
 4.10.2 current 매크로란
4.11 프로세스 디버깅
 4.11.1 glibc fork 함수 gdb 디버깅
 4.11.2 유저 프로그램 실행 추적 


핑백

덧글

댓글 입력 영역