동적 타이머란

  • 현재 시점으로 미래의 시점(jiffies)에 동적 타이머가 만료
  • 동적 타이머가 만료될 때 동적 타이머 핸들러 함수 호출

동적 타이머와 디바이스 드라이버

  • 디바이스 드라이버에서 동적 타이머는 많이 사용됨
  • 디버깅 정보 출력, 특정 시간 후에 루틴을 실행

동적 타이머 실행 흐름

  1. 동적 타이머 초기화
  • timer_setup()
  1. 동적 타이머 등록
  • add_timer(), mod_timer(),
  1. 동적 타이머 실행
  • run_timer_softrq, run_timers(), call_timer, collect expired timers()

    동적 타이머

  • 1단계: 동적 타이머 초기화

    • 보통 부팅 단계에서 설정
  • 2단계: 동적 타이머 등록

    • 동적 타이머의 만료 시간을 1/HZ 단위로 지정한 다음 add_timer() 혹은 mod_timer()
      함수를 호출
  • 3단계: 동적 타이머 실행

    • 동적 타이머가 지정한 만료 시간이 되면 동적 타이머를 실행
    • 동적 타이머가 실행되면 동적 타이머 콜백 함수가 호출됨

§ timer_setup() 함수 선언부

#define timer_setup(timer, callback, flags) 
__init_timer((timer), (callback), (flags))
void timer_setup(struct timer_list *timer, void *func, unsigned int flags);
  • struct timer_list *timer: 동적 타이머를 나타내는 정보
  • void *func: 동적 타이머 핸들러 함수
  • unsigned int flags: 동적 타이머 플래그

struct timer_list 구조체

struct timer_list {
struct hlist_node entry;
unsigned long expires; // 지피스 만료 시각이 1700이면 1701에 동적 타이머 핸들러 실행
void (*function)(struct timer_list *);
u32 flags;
#ifdef CONFIG_LOCKDEP 
struct lockdep_map lockdep_map;
#endif
};
  • entry: 해시 연결 리스트로 timer_bases 전역변수에 동적 타이머를 등록할 때 사용됨
  • expires: 동적 타이머 만료 시각 저장: jiffies (단위: 1/HZ)
  • function: 동적 타이머 핸들러 함수의 주소를 저장
  • flags: 동적 타이머의 설정 필드

bcm2835_sdhost_add_host() 함수

int bcm2835_sdhost_add_host(struct bcm2835_host *host)
{
struct mmc_host *mmc;
...
tasklet_init(&host->finish_tasklet,
bcm2835_sdhost_tasklet_finish, (unsigned long)host);
INIT_WORK(&host->cmd_wait_wq, bcm2835_sdhost_cmd_wait_work);
timer_setup(&host->timer, bcm2835_sdhost_timeout, 0);
  • host->timer: timer_list 구조체의 주소
  • bcm2835_sdhost_timeout: 동적 타이머 핸들러 함수
  • 0: timer_list 구조체의 flags 플래그 설정 값

동적 타이머를 초기화 관련 디버깅

crash64> timer
JIFFIES
4295945208 // 지피스값
TIMER_BASES[0][BASE_STD]: ffffff80fb779d00
EXPIRES TTE TIMER_LIST FUNCTION
4295945270 62 ffffff8042042910 ffffffd174aa8c00 <input_repeat_key> // 만료되지않은 동적타이머 정보, 5271이 되면 만료된다.
kdt_crash> struct timer_list ffffff8042042910
struct timer_list {
entry = {
next = 0x0,
pprev = 0xffffff80fb779fa8
},
expires = 4295945270, // 지피스값 271이 되면 동적 타이머 호출
function = 0xffffffd174aa8c00 <input_repeat_key>, // 동적타이머 핸들러 함수
flags = 297795584
}

2단계: 동적 타이머 등록

  • add_timer()
  • mod_timer()
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/time/timer.c
void add_timer(struct timer_list *timer) 
{
BUG_ON(timer_pending(timer));
__mod_timer(timer, timer->expires, MOD_TIMER_NOTPENDING);
}
https://elixir.bootlin.com/linux/v5.15.30/source/kernel/time/timer.c
int mod_timer(struct timer_list *timer, unsigned long expires)
{
return __mod_timer(timer, expires, 0);
}
EXPORT_SYMBOL(mod_timer);

실행흐름

  1. 타이머 인터럽트 처리
    raise_softirq_irqoff
    raise_softirq
    update_process_times
    tick_sched_handle
    tick_sched_timer
    __hrtimer_run_queues
    hrtimer_interrupt
    arch_timer_handler_phys
    handle_percpu_devid_irq
    generic_handle_domain_irq
    gic_handle_irq
    call_on_irq_stack
    do_interrupt_handler
    el1_interrupt
    el1h_64_irq_handler
    el1h_64_irq // 인터럽트

  2. SOFT IRQ 서비스 실행

run_timer_softirq
do_softirq
__
do_softirq
call_on_irq_stack+0x
do_softirq_own_stack
irq_exit_rcu

  • 만료되는 동적타이머가 있으면 핸들러를 호출
  1. 타이머 처리

동적 타이머 핸들러 호출
call_timer_fn
expire_timers

  • run_timer_softirq() 함수는 1초에 HZ 만큼 실행

  • 만료될 동적 타이머가 있는지 체크한 후 동적 타이머 핸들러 호출

  • __run_timers() 함수
    동적 타이머의 만료 조건을 체크하고 예외 처리 수행
    expire_timers() 함수를 호출

  • collect_expired_timers() 함수
    __collect_expired_timers() 함수 호출

  • __collect_expired_timers() 함수
    현재 커널 타이머 해시 테이블에 등록된 동적 타이머 개수를 반환

  • expire_timers() 함수
    만료된 동적 타이머의 timer_list 구조체 주소를 로딩해 call_timer_fn() 함수 호출

  • call_timer_fn() 함수
    동적 타이머 핸들러 함수를 직접 호출

https://elixir.bootlin.com/linux/v5.15.30/source/kernel/time/timer.c
static void call_timer_fn(struct timer_list *timer,
void (*fn)(struct timer_list *),
unsigned long baseclk)
{
int count = preempt_count();
...
trace_timer_expire_entry(timer, baseclk);
fn(timer); // 포인터를 처리하는 구문 fn은 동적타이머 핸들러의 주소를 담고있음
trace_timer_expire_exit(timer); 

ftrace

kworker/u9:2-807 [000] d..1. 1215.509638: timer_start: timer=000000002a7014b5 
function=brcmf_sdio_watchdog [brcmfmac] expires=4295196172 [timeout=3] cpu=0 idx=13 // 만료시간 172ㅡ 173되는 순간 동적 타이머 실행 flags=
...
<idle>-0 [000] d.s2. 1215.525068: timer_cancel: timer=000000002a7014b5
<idle>-0 [000] ..s1. 1215.525070: timer_expire_entry: timer=000000002a7014b5 function=brcmf_sdio_watchdog
[brcmfmac] now=4295196173 baseclk=4295196173 // 173이면 동적 타이머가 실행이 됨
<idle>-0 [000] ..s1. 1215.525072: brcmf_sdio_watchdog+0x4/0x70 [brcmfmac] <-call_timer_fn+0x38/0x1d0
<idle>-0 [000] ..s1. 1215.525074: <stack trace> // 동적타이머 실행시 스택 트레이스
=> brcmf_sdio_watchdog+0x8/0x70 [brcmfmac]
=> call_timer_fn+0x38/0x1d0
=> run_timer_softirq+0x284/0x520
=> __do_softirq+0x1a0/0x4d8
=> ____do_softirq+0x18/0x28
=> call_on_irq_stack+0x2c/0x54
=> do_softirq_own_stack+0x24/0x30
…
<idle>-0 [000] ..s1. 1215.525081: timer_expire_exit: timer=000000002a7014b5

0개의 댓글