본문 바로가기

Core BSP 분석/리눅스 커널 핵심 분석

[Linux][Kernel] 타이머(Timer) Overview

리눅스 커널에서 아주 중요한 Subsystem 중 하나인 타이머에 대해서 간단히 짚어 볼께요.
 
init_timer
아래 함수 콜로 do_init_timer() 함수에서 struct timer_list *timer 초기화를 수행해요.
__init_timer((timer), 0)
init_timer_key((_timer), (_flags), #_timer, &__key);
do_init_timer(timer, flags, name, key);
 
MIPS 아키텍쳐에서 아래와 같이 타이머를 세팅하는 코드가 있네요.
static inline void ip32_power_button(void)
{
// .. 생략..
blink_timer.data = POWERDOWN_FREQ;
blink_timeout(POWERDOWN_FREQ);
 
init_timer(&power_timer); //<<--
 
 
코드 리뷰 좀 할께요.
[1]: __get_timer_base(flags); 함수 호출로 per-cpu 타입의 struct tvec_base *base 인스턴스를 가져와요
[2]: timer->entry.next 를 NULL 로 초기화하죠
 
static void do_init_timer(struct timer_list *timer, unsigned int flags,
      const char *name, struct lock_class_key *key)
 {
  struct tvec_base *base;
 
  base = __get_timer_base(flags); //<<--[1]
 
  timer->entry.next = NULL;  // <--[2]
  timer->base = (void *)((unsigned long)base | flags); //<<--[3]
  timer->slack = -1;
 #ifdef CONFIG_TIMER_STATS
  timer->start_site = NULL;
  timer->start_pid = -1;
  memset(timer->start_comm, 0, TASK_COMM_LEN);
 #endif
  lockdep_init_map(&timer->lockdep_map, name, key, 0);
 
 
음, 그럼 디버깅은 어떻게 하냐구요?
T32 명령어로 per-cpu 0 타입일 때 (struct tvec_base* *)(((void*)&tvec_bases)+__per_cpu_offset[0]) 명령어로,
struct tvec_base *base 인스턴스를 가져올 수 있어요.
 
아래 구조체 감상하시구요. 타이머 주기 별로 등록한 256, 64, 64, 64, 64 사이즈의 해시 테이블이 보이네요.
  (struct tvec_base * *) (struct tvec_base* *)(((void*)&tvec_bases)+__per_cpu_offset[0]) = 0xC4B467B8 -> 0xC14B6940 -> (
    (spinlock_t) lock = ((struct raw_spinlock) rlock = ((arch_spinlock_t) raw_lock = ((u32) slock = 3907315940, (struct __raw_
    (struct timer_list *) running_timer = 0x0,
    (long unsigned int) timer_jiffies = 436745,
    (long unsigned int) next_timer = 435902,
    (long unsigned int) active_timers = 15,
    (struct tvec_root) tv1 = (
      (struct list_head [256]) vec = (((struct list_head *) next = 0xC14B6954, (struct list_head *) prev = 0xC14B6954), ((stru
    (struct tvec) tv2 = ((struct list_head [64]) vec = (((struct list_head *) next = 0xC14B7154, (struct list_head *) prev = 0
    (struct tvec) tv3 = ((struct list_head [64]) vec = (((struct list_head *) next = 0xC14B7354, (struct list_head *) prev = 0
    (struct tvec) tv4 = ((struct list_head [64]) vec = (((struct list_head *) next = 0xC14B7554, (struct list_head *) prev = 0
    (struct tvec) tv5 = ((struct list_head [64]) vec = (((struct list_head *) next = 0xC14B7754, (struct list_head *) prev = 0
 
static inline struct tvec_base *__get_timer_base(unsigned int flags)
 {
  if (flags & TIMER_DEFERRABLE)
   return &tvec_base_deferrable;
  else
   return raw_cpu_read(tvec_bases);  //<<--
 
 
add_timer
핵심 함수들 목록은 아래와 같은데, 분석이 필요한 함수에 대해서 좀 더 살펴보죠
mod_timer(timer, timer->expires);
__mod_timer(timer, expires, false, TIMER_NOT_PINNED);
internal_add_timer(base, timer);
__internal_add_timer(base, timer);
 
 
샘플 코드는 여기..
static inline void ip32_power_button(void)
{
// .. 생략..
init_timer(&power_timer);
power_timer.function = power_timeout;
power_timer.expires = jiffies + POWERDOWN_TIMEOUT * HZ;
add_timer(&power_timer); //<<--
}
 
 
[1]: __get_timer_base() 함수를 통해서 업데이트된 timer->base 인스턴스를 가져와요.
[2]: expires 값에 따라 timer hash vector에 timer->entry.next를 추가하네요
 
static inline int
 __mod_timer(struct timer_list *timer, unsigned long expires,
       bool pending_only, int pinned)
 {
  struct tvec_base *base, *new_base;
  unsigned long flags;
  int ret = 0 , cpu;
 
  timer_stats_timer_set_start_info(timer);
  BUG_ON(!timer->function);
 
  base = lock_timer_base(timer, &flags);//<<--[1]
 
  ret = detach_if_pending(timer, base, false);  
  if (!ret && pending_only)
   goto out_unlock;
 
  debug_activate(timer, expires);
 
 //snip
 
  timer->expires = expires;
  internal_add_timer(base, timer); //<<--[2]
 
 
lock_timer_base() 함수
[1]: timer->base에 저장된 per-cpu 타입 타이머 해시 테이블 주소를 가져오네요
[2]: TIMER_FLAG_MASK bit masking을 하구요.
 
static struct tvec_base *lock_timer_base(struct timer_list *timer,
      unsigned long *flags)
  __acquires(timer->base->lock)
 {
  struct tvec_base *base;
 
  for (;;) {
   struct tvec_base *prelock_base = timer->base; //<<--[1]
   base = tbase_get_base(prelock_base); //<<--[2]
   if (likely(base != NULL)) {
    spin_lock_irqsave(&base->lock, *flags);
    if (likely(prelock_base == timer->base))
     return base;
    /* The timer has migrated to another CPU */
    spin_unlock_irqrestore(&base->lock, *flags);
   }
   cpu_relax();
 
 static inline struct tvec_base *tbase_get_base(struct tvec_base *base)
 {
  return ((struct tvec_base *)((unsigned long)base & ~TIMER_FLAG_MASK));
 } 
 
 
현재 timer base jiffies 값 base->timer_jiffies과 비교하여 차이나는 만큼 
timer hash table을 가져오는 코드에요
[1]: 전처리문으로 보면 아래 코드와 같아요
if (idx < (1 << (0 ? 6 : 8))) //<<-- 100
[1.1] 아래 operation으로 expires 값에 가장 맞는 hash vec 를 가져와요
int i = expires & ((1 << (0 ? 6 : 8)) - 1);
[2]: idx < 4096 = idx < ( 1 << 12)
else if (idx < 1 << ((0 ? 6 : 8) + (0 ? 4 : 6))) { //<<--4096
[3]: idx < 1048576(0x100000) = idx < ( 1 << 20)
else if (idx < 1 << ((0 ? 6 : 8) + 2 * (0 ? 4 : 6))) {
 
static void
 __internal_add_timer(struct tvec_base *base, struct timer_list *timer)
 {
  unsigned long expires = timer->expires;
  unsigned long idx = expires - base->timer_jiffies;
  struct list_head *vec;
 
  if (idx < TVR_SIZE) {  //<<-[1]
   int i = expires & TVR_MASK;  //<<--[1.1]
   vec = base->tv1.vec + i;
  } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {  //<<--[2]
   int i = (expires >> TVR_BITS) & TVN_MASK;
   vec = base->tv2.vec + i;
  } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) { //<<--[3]
   int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
   vec = base->tv3.vec + i;
  } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) { //<<--[4]
   int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
   vec = base->tv4.vec + i;
  } else if ((signed long) idx < 0) {
   /*
    * Can happen if you add a timer with expires == jiffies,
    * or you set a timer to go off in the past
    */
   vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
  } else {
 //snip 
   if (idx > MAX_TVAL) {
    idx = MAX_TVAL;
    expires = idx + base->timer_jiffies;
   }
   i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
   vec = base->tv5.vec + i;
  }
  /*
   * Timers are FIFO:
   */
  list_add_tail(&timer->entry, vec);
 
 
넥서스 9 보드에서 추출한 전처리 파일은 아래와 같은데요, 매크로가 풀이니 뭐 의미인지 모르겠네요.
 
 static void
  __internal_add_timer(struct tvec_base *base, struct timer_list *timer)
  {
   unsigned long expires = timer->expires;
   unsigned long idx = expires - base->timer_jiffies;
   struct list_head *vec;
 
   if (idx < (1 << (0 ? 6 : 8))) {
    int i = expires & ((1 << (0 ? 6 : 8)) - 1);
    vec = base->tv1.vec + i;
   } else if (idx < 1 << ((0 ? 6 : 8) + (0 ? 4 : 6))) {
    int i = (expires >> (0 ? 6 : 8)) & ((1 << (0 ? 4 : 6)) - 1);
    vec = base->tv2.vec + i;
   } else if (idx < 1 << ((0 ? 6 : 8) + 2 * (0 ? 4 : 6))) {
    int i = (expires >> ((0 ? 6 : 8) + (0 ? 4 : 6))) & ((1 << (0 ? 4 : 6)) - 1);
    vec = base->tv3.vec + i;
   } else if (idx < 1 << ((0 ? 6 : 8) + 3 * (0 ? 4 : 6))) {
    int i = (expires >> ((0 ? 6 : 8) + 2 * (0 ? 4 : 6))) & ((1 << (0 ? 4 : 6)) - 1);
    vec = base->tv4.vec + i;
   } else if ((signed long) idx < 0) {
    vec = base->tv1.vec + (base->timer_jiffies & ((1 << (0 ? 6 : 8)) - 1));
   } else {
    int i;
 
    if (idx > ((unsigned long)((1ULL << ((0 ? 6 : 8) + 4*(0 ? 4 : 6))) - 1))) {
     idx = ((unsigned long)((1ULL << ((0 ? 6 : 8) + 4*(0 ? 4 : 6))) - 1));
     expires = idx + base->timer_jiffies;
    }
    i = (expires >> ((0 ? 6 : 8) + 3 * (0 ? 4 : 6))) & ((1 << (0 ? 4 : 6)) - 1);
    vec = base->tv5.vec + i;
   }
 
   list_add_tail(&timer->entry, vec);
 
 
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2