리눅스 커널에서 아주 중요한 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
'Core BSP 분석 > 리눅스 커널 핵심 분석' 카테고리의 다른 글
와치독 Watchdog Kick 동작 비교 (Qualcomm vs Intel vs Mediatek vs nVidia) (0) | 2023.05.06 |
---|---|
Reboot - Kernel Rebooting(커널 리부팅) Sequence (0) | 2023.05.06 |
파레트 (0) | 2023.05.06 |
[Kernel]panic @__wake_up (0) | 2023.05.06 |
세마포어 (0) | 2023.05.06 |