Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

5363
1898
209234


[리눅스커널] 시간관리: Soft IRQ 타이머 서비스 실행 시작 단계 코드 분석 8. 커널 타이머 관리

이어서 커널 시스템 타이머 처리 3단계를 알아봅시다. 다음 그림에서 검은색으로 된 부분입니다.  
 
[그림 8.7] 커널 타이머 전체 흐름도에서 Soft IRQ 타이머 서비스 실행 단계
 
1~2 단계에서 타이머 인터럽트가 발생하면 TIMER_SOFTIRQ 아이디로 Soft IRQ 서비스 요청을 했습니다. 이후 타이머 인터럽트 핸들링이 끝나면 Soft IRQ 서비스를 시작하는 irq_exit() 함수를 호출합니다. 인터럽트 컨택스트가 끝나고 Soft IRQ 컨택스트가 시작하는 시점입니다.

TIMER_SOFTIRQ 아이디로 Soft IRQ 서비스 요청을 했으니 다음 순서로 함수를 호출합니다.  
irq_exit()
invoke_softirq()
__do_softirq()

커널 타이머는 Soft IRQ 서비스 중 하나로 실행합니다. 따라서 Soft IRQ 서비스를 실행하는 __do_softirq() 함수를 분석할 필요가 있습니다. 6.2절에서 배운 내용을 떠 올리며 Soft IRQ 서비스 핸들러를 호출하는 __do_softirq() 함수를 분석하겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/softirq.c]
01 asmlinkage __visible void __softirq_entry __do_softirq(void)
02 {
03 unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
04 unsigned long old_flags = current->flags;
05 struct softirq_action *h;
...
06 restart:
07 set_softirq_pending(0);
08
09 local_irq_enable();
10
11 h = softirq_vec;
12 while ((softirq_bit = ffs(pending))) {
13 unsigned int vec_nr;
14 int prev_count;
15
16 h += softirq_bit - 1;
17
18 vec_nr = h - softirq_vec;
19 prev_count = preempt_count();
20
21 kstat_incr_softirqs_this_cpu(vec_nr);
22
23 trace_softirq_entry(vec_nr);
24 h->action(h);

먼저 11번째 줄 코드를 보겠습니다.
11 h = softirq_vec;

Soft IRQ 서비스별 핸들러 함수 주소를 담고 있는 softirq_vec를 h에 저장합니다.

다음 16번째 줄 코드입니다.
16 h += softirq_bit - 1;

Soft IRQ 서비스가 활성화된 비트 정보인 softirq_bit를 h에 저장합니다.

이후 24번째 줄에서는 Soft IRQ 서비스 핸들러 함수를 호출합니다. 
커널 타이머 관점으로 보면 이 부분에서 TIMER_SOFTIRQ 서비스 핸들러인 run_timer_softirq() 함수를 호출합니다.


softirq_vec 변수는 다음과 같습니다.  
  (static struct softirq_action [10]) softirq_vec = (
    [0] = ((void (*)()) action = 0x8012A364 = tasklet_hi_action),
    [1] = ((void (*)()) action = 0x801856A8 = run_timer_softirq),
    [2] = ((void (*)()) action = 0x80D02910 = net_tx_action),
    [3] = ((void (*)()) action = 0x80D04A78 = net_rx_action),
    [4] = ((void (*)()) action = 0x8038DC98 = blk_done_softirq),
    [5] = ((void (*)()) action = 0x8038E28C = blk_iopoll_softirq),
    [6] = ((void (*)()) action = 0x8012A494 = tasklet_action),
    [7] = ((void (*)()) action = 0x80162CCC = run_rebalance_domains),
    [8] = ((void (*)()) action = 0x801881F0 = run_hrtimer_softirq),
    [9] = ((void (*)()) action = 0x8017ED78 = rcu_process_callbacks))

위에서 분석한 24번째 줄 코드에서는 1번째 배열 인덱스에 있는 run_timer_softirq() 함수를 호출합니다.


여기까지 3단계 커널 타이머 처리 과정을 요약하면 다음과 같습니다. 
1. 타이머 인터럽트가 발생하면 TIMER_SOFT IRQ Soft IRQ 서비스를 요청
2. Soft IRQ 서비스를 실행할 때 TIMER_SOFTIRQ 서비스 아이디 핸들러인 run_timer_softirq() 함수를 호출
  ; 본격적으로 커널 타이머 관련 함수를 실행해서 동적 타이머 실행 시작

다음 커널 시스템 타이머 전체 흐름도에서 4단계를 분석할 차례입니다.
 
[그림 8.8] 커널 타이머 전체 흐름도에서 동적 타이머 실행 단계

마지막 4단계인 커널 시스템 타이머가 동적 타이머 핸들러 함수를 호출하는 동작을 살펴보겠습니다.
run_timer_softirq()
__run_timers()

위 함수는 다음과 같은 처리를 합니다.
동적 타이머를 실행할 조건을 점검합니다. 
현재 시각 정보와 만료될 동적 타이머 시각 정보를 비교해 동적 타이머를 실행할지 결정합니다.

run_timer_softirq() 함수 분석
주석문을 지우고 본 run_timer_softirq() 함수 구현부는 다음과 같습니다. 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
1 static __latent_entropy void run_timer_softirq(struct softirq_action *h)
2 {
3 struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
4 base->must_forward_clk = false;
5
6 __run_timers(base);
7 if (IS_ENABLED(CONFIG_NO_HZ_COMMON))
8 __run_timers(this_cpu_ptr(&timer_bases[BASE_DEF]));
9 }

3번째 줄을 보면 per-cpu 타입 timer_bases 전역 변수를 per-cpu 오프셋을 적용해 base 에 저장합니다. 만약 run_timer_softirq() 함수가 CPU1에서 구동 중이면 CPU1에 해당하는 struct timer_base 구조체 주소를 읽습니다.

base 지역 변수는 6번째 줄 코드와 같이 __run_timers() 함수 인자로 전달합니다.

__run_timers() 함수 분석
이어서 __run_timers() 함수를 분석하겠습니다. 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/time/timer.c]
1 static inline void __run_timers(struct timer_base *base)
2 {
3 struct hlist_head heads[LVL_DEPTH];
4 int levels;
5
6 if (!time_after_eq(jiffies, base->clk))
7 return;
8
9 raw_spin_lock_irq(&base->lock);
10
11 while (time_after_eq(jiffies, base->clk)) {
12
13 levels = collect_expired_timers(base, heads);
14 base->clk++;
15
16 while (levels--)
17 expire_timers(base, heads + levels);
18 }
19 base->running_timer = NULL;
20 raw_spin_unlock_irq(&base->lock);
21}

먼저 6번째 줄 코드를 봅시다.
6 if (!time_after_eq(jiffies, base->clk))
7 return;

현재 시각 정보인 jiffies가 base->clk 값보다 작으면 7 번째 줄 코드를 실행해 함수 실행을 종료합니다. base->clk에는 어떤 값이 있길래 이 조건을 보고 함수를 빠져나올까요? 

    등록한 동적 타이머 중 가장 먼저 만료할 동적 타이머의 HZ 단위 jiffies 값입니다.
 
만약 현재 jiffies가 1100이고 base->clk 값이 1104이면 __run_timers() 함수 실행을 종료합니다. 동적 타이머를 실행한 시점이 아니기 때문입니다.

만약 커널 타이머 기준으로 4번 do_timer() 함수가 호출돼 jiffies가 1104가 되면 __run_timers() 함수를 실행합니다.

이해를 돕기 위해 한 가지 예를 들겠습니다.
여러분이 아침 6시에 타이머 알람을 맞췄는데, 현재 시간이 5시 50분이라고 가정합시다. 5시 50분은 알람이 울릴 시간이 아니고 10분 후인 6시에 알람이 울려야 합니다. 

마찬가지로 6~7 번째 줄 코드는 현재 시간이 동적 타이머를 실행할 시점인지 점검하는 동작입니다. 만약 현재 시각이 등록된 동적 타이머 만료 시간보다 이른 시간이면 타이머를 실행할 필요가 없습니다.

이제 __run_timers() 함수 핵심 코드인 11번째 줄을 봅시다.
11 while (time_after_eq(jiffies, base->clk)) {
12
13 levels = collect_expired_timers(base, heads);
14 base->clk++;
15
16 while (levels--)
17 expire_timers(base, heads + levels);
18 }

jiffies가 base->clk 값보다 같거나 클 경우 while 루프는 실행합니다. 
6번째 줄 코드에 이어 다시 동적 타이머를 실행할 조건을 점검합니다.

13번째 줄 코드에서 collect_expired_timers() 함수를 호출해 반환값을 levels 변수에 저장합니다. levels은 현재 등록된 동적 타이머 개수를 저장합니다. 또한 heads로 타이머가 위치한 벡터 해시 테이블 주소를 읽어 옵니다.

14번째 줄은 base->clk 값을 +1만큼 증감합니다. 만약 base->clk를 1만큼 증감하지 않으면 다음에 타이머 인터럽트가 발생해서 위 코드가 실행할 때 커널 시스템 타이머가 동적 타이머가 등록된 것으로 판단합니다.

16~17번째 줄은 levels 변수를 -1만큼 감소시키며 expire_timers() 함수를 호출합니다. 동적 타이머 갯수 만큼 expire_timers() 함수를 호출해서 만료된 동적 타이머 함수를 호출합니다.

* 강의 동영상도 있으니 같이 들으시면 좋습니다.




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

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

# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2


 



핑백

덧글

  • 하루n 2018/08/31 17:37 # 삭제 답글

    예전부터 리눅스 커널 책을 사서 보곤 했지만 어려워서 중도포기 했었습니다
    그런데 여기 강좌를 보고 감동했습니다 그동안 이렇게 커널에 대해서 자세하고 여러 방식을 통해서 효과적으로 알려주신 분은 없었습니다. 정말 감사합니다!
  • Guillermo 2018/08/31 18:45 #

    하루n님.. 제글을 읽어주시니 제가 감사하죠. 글을 읽고 혹시 궁금한 점이 있으면 답글 달아 주세요. 자세히 답신 드리겠습니다. 참고로, 여기 올리는 글들은 제가 쓰고 있는 리눅스 커널 책 1차 원고 내용입니다.
  • 하루n 2018/08/31 17:54 # 삭제 답글

    Trace32도 꼭 써보고 싶습니다만, 가격이 가격인지라 ARM11 까지 지원하는 구형 trace32에 라즈베리파이 1 (ARM11)을 물려서 리눅스 커널 디버깅을 보고 따라 해볼까 합니다만 어떨지요?
  • Guillermo 2018/08/31 18:42 #

    라즈베리파이에 t32을 연결하는 것도 좋지만 t32 시뮬레이터를 활용해도 좋습니다. 메일 주소 알려주시면 방법 공유드리겠습니다.
  • 하루n 2018/08/31 19:28 # 삭제 답글

    아 그러셨군요 매우 기다려집니다! 책 출판하시면 바로 구매하고 주위에도 널리 알리겠습니다
    제 메일 주소는 nscd2309@naver.com 입니다 그럼 잘 부탁드리겠습니다
  • Guillermo 2018/08/31 22:05 #

    네, 내용 정리하면 메일로 공유드릴께요. 즐거운 주말 되세요.
  • Padawan 2018/09/18 17:57 # 삭제 답글

    안녕하세요.1년 전 부터 리눅스 커널을 공부 하고 있는 직장인 입니다.
    Kernel debugging, ftrace 같은 걸 사용하면 커널을 이해하는데 도움이 많이 될 것 같다는 생각을 늘 했는데..
    ftrace나 Trace32같은 HW디버거를 이용해서 설명해 주신 건 국내는 물론 외국 사이트 에서도 찾기 어려운 자료인 것 같습니다.
    커널 책을 내실 계획이라고 하니 빨리 보고 싶네요^^
    늘 좋은 자료 올려 주셔서 감사드립니다.
  • Guillermo 2018/09/19 08:09 #

    리눅스 커널에 대한 제 블로그 글을 관심있게 읽어주셔서 정말 감사합니다.
    글 중에 부족한 점이 보이면 답글 달아주시면 상세히 답글로 답신 드리겠습니다.

    책은 내년 1~2월 정도 출간 예정입니다.
  • Padawan 2018/09/20 14:54 # 삭제 답글

    괜찮으시다면 저도 T32 시뮬레이터를 이용한 커널 디버깅 방법을 알려 주실 수 있으신가요?
    제 메일 주소는 seunghwan.mun@outlook.kr 입니다.^^
  • Guillermo 2018/09/20 15:53 #

    내용 정리하면 메일로 드리겠습니다.
  • 장홍석 2018/11/27 11:12 # 삭제 답글

    T32 시뮬레이터를 이용한 커널 디버깅 방법 좀 알려 주시면 고맞겠습니다.
    제 메일 주소는 jhs01251@gmail.com 입니다. 감사합니다.
  • Guillermo 2018/11/27 21:14 #

    요즘 집필로 정신이 없네요. 내용 정리하면 메일로 드리겠습니다.
  • isempty 2018/12/26 18:19 # 삭제 답글

    linux 상에서 T32를 통한 vmcore 나 커널을 디버깅하는 방법에 대해 자세히 나와있는 자료 나 링크가 있으면 부탁드려도 될까요? 제 이멜주소는 isempty.lkcd@gmail.com 입니다.
  • AustinKim 2018/12/26 18:35 #

댓글 입력 영역