티스토리 뷰
9 시간과 타이머 관리
9.3 타이머 관리
9.3.1 타이머 개요
- 과거 리눅스의 클래식 타이머는 HZ 단위 (1초에 n 번) 로 동작
- 밀리초 단위로 동작하여 고해상도 처리를 하지 못해, 다른 기법들이 요구됨
타이머의 용도
- 타임 아웃: 지정된 시간안에 조건이나 이벤트가 발생하기를 기다림. (네트워크의 패킷을 대기)
- 타이머: 지정된 시간 후에 일을 처리함
타이머의 요구사항
- 전력 사용 측면: 제한된 전격 용량을 사용하는 경우 에너지를 줄임.
- 고해상도의 필요: 고해상도 타이머는 세밀한 단위로 시간(clock) 을 제공하는 디바이스가 시스템에 존재해야함. 나노초 단위로 관리
타이머의 구성요소
- clock chip 을 CCF 등 추상화된 프레임 워크로 사용
- 클록 이벤트 서브 시스템을 사용하여 주기적 or oneShot 을 처리
9.3.2 저해상도 타이머
타이머 활성화 및 프로세스 사용량
HZ 단위로 사용. 상호작용이 중요하지 않은 서버 및 배치 시스템은 오히려 HZ 값이 낮을 수록 좋은 평가를 받기도 함
- do_timer: 하나의 cpu 에서 jiffies 값을 갱신. 다른 cpu 는 해당 작업을 수행하지 않음
- update_process_time: smp 시스템의 모든 cpu 에서 수행. 모든 저해상도 타이머를 활성화, 만료 하고 스케줄링 처리
타이머 인터럽트가 발생하면, IRQ
[EL0 - 유저 앱 실행 중]
↓ 타이머 IRQ 발생
[GIC 인터럽트 컨트롤러]
↓ IRQ → EL1로 라우팅됨 (기본 설정)
[CPU 진입: EL1]
↓
[EL1 인터럽트 핸들러 실행] 인터럽트 번호를 가져와서 호출
↓
[OS 타이머 핸들러 호출]
entry.S
ENTRY(vectors)
ventry el1_sync_invalid // Synchronous EL1t
ventry el1_irq_invalid // IRQ EL1t
ventry el1_fiq_invalid // FIQ EL1t
ventry el1_error_invalid // Error EL1t
jiffies
시스템 부팅후 발생한 틱 수를 나타내는 전역 변수
// jiffies.h
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
// kernel/time/timer.c
__visible u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;
EXPORT_SYMBOL(jiffies_64);
타이머 관리 데이터 구조
타이머 별로 하나의 틱이 얼마의 시간인지 다르기 때문에, jiffies 에 원하는 값을 더해서 사용한다
약자 | 명칭 | 설명 |
---|---|---|
TVR | Timer Vector Root | 가장 낮은 레벨 (즉시 만료에 가까운 타이머) |
TVN | Timer Vector Next | 더 멀리 있는 만료 시각용 타이머들을 저장하는 상위 레벨 |
// kernel/time/timer.c
#define TVN_BITS (CONFIG_BASE_SMALL ? 4 : 6)
#define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)
#define MAX_TVAL ((unsigned long)((1ULL << (TVR_BITS + 4*TVN_BITS)) - 1))
struct tvec {
struct hlist_head vec[TVN_SIZE];
};
struct tvec_root {
struct hlist_head vec[TVR_SIZE];
};
struct tvec_base {
spinlock_t lock;
struct timer_list *running_timer; // 특정 프로세서에서 대해 현재 실행중인 타이머
unsigned long timer_jiffies; // 아직 체크해야할 dynamic 타이머들의 만료시간 중 가장 빨리 만료되는 값
unsigned long next_timer; // 다음 타이머 인터럽트에 대한 보류시간
unsigned long active_timers; // 활성화된 타이머. (연기할 수 없는 타이머가 존재할때)
unsigned long all_timers; // active_timers + deferrable timers
int cpu; // 타이머를 소유한 프로세서
bool migration_enabled; // 타이머의 다른 프로세서로 이동 여부
bool nohz_active;
struct tvec_root tv1; // 가까운 타이머용
struct tvec tv2; // 시간이 지나면서 점점 cascade. 한단계씩 내려옴 (아래 __run_timers)
struct tvec tv3;
struct tvec tv4;
struct tvec tv5;
} ____cacheline_aligned;
9-5 kernel/time/timer.c init_timers
void __init init_timers(void)
{
init_timer_cpus(); // 각 cpu 의 tvec_base 를 현재 jiffies 값으로 초기화
init_timer_stats();
timer_register_cpu_notifier();
open_softirq(TIMER_SOFTIRQ, run_timer_softirq); // irq 의 타이머 액션을 등록
}
static void run_timer_softirq(struct softirq_action *h)
{
struct tvec_base *base = this_cpu_ptr(&tvec_bases);
if (time_after_eq(jiffies, base->timer_jiffies)) // timer_jiffies 를 넘어가면 돌려주자
__run_timers(base);
}
static inline void __run_timers(struct tvec_base *base)
{
if (!index &&
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3));
}
9.3.3 고해상도 타이머
- 고해상도 타이머는 레드 블랙 트레이서 시간 순서대로 정렬된다.
- 고해상도 타이머는 주기적인 틱에 독립적이며 jiffies 를 사용하지 않고 나노초 타임 스탬프를 사용한다.
데이터 구조
- monotonic : 부팅 후부터 시작한 나노 타임
- realtime : 실제 세계 시간
- ...
고해상도 타이머의 특징
- 고해상도 타이머는 저해상도 모드에서도 구동이 가능하다.
- 부팅중 or 커널에서 고해상도 모드 지원하지 않는 경우.
해상도 별 인터럽트 처리
타이머 모드 | 흐름 | 설명 |
---|---|---|
고해상도 모드 | hrtimer_interrupt() → __hrtimer_run_queues() |
디바이스 (clock source)에서 인터럽트 호출. |
저해상도 모드 | run_local_timers() → __hrtimer_run_queues() |
주기적인 cpu tick 인터럽트 |
9-6 kernel/time/hrtimer.c hrtimer_run_queues
상태 | hrtimer_is_hres_enabled() | allow_nohz 값 | 의미 |
---|---|---|---|
고해상도 타이머 활성화 | true | false | nohz 불필요 (이미 hrtimer 정확) |
고해상도 타이머 비활성 | false | true | nohz 필요 (틱 줄여야 함) |
void hrtimer_run_queues(void)
{
if (__hrtimer_hres_active(cpu_base)) // 고해상도 타이머가 활성되어있으면 처리할 필요가 없음. interrupt 에서 처리할테니
return;
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) { // 시스템이 nohz (tickless) 로 변경 될 수 있는지 확인
hrtimer_switch_to_hres();
return;
}
now = hrtimer_update_base(cpu_base); // monotonic 클록 기준으로 offset 갱신
__hrtimer_run_queues(cpu_base, now); // 타이머 구동
}
HRTIMER_NORESTART -> 이걸로 oneshot, periodic 구분하는듯
// allow_nohz가 true면:
// → 정말로 nohz로 바꿀지 판단하고, 가능하면 전환까지 함
// allow_nohz가 false면:
// → 전환은 안 하지만, 시스템이 nohz 가능한지 확인만 함
int tick_check_oneshot_change(int allow_nohz)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
if (!test_and_clear_bit(0, &ts->check_clocks)) // 상태 체크가 필요한지 확인. 한번만 하기 위함인 것으로 보임
return 0;
if (ts->nohz_mode != NOHZ_MODE_INACTIVE) // 이미 nohz 모드인 경우 무시
return 0;
if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available()) // 고해상도 모드 조건 체크
return 0;
if (!allow_nohz)
return 1;
tick_nohz_switch_to_nohz(); // nohz 모드로 전환
return 0;
}
- tick_nohz_switch_to_nohz : 현재 cpu 가 주기적인 틱이 아니라 oneshot 모드로 다음 필요한 타이머만 예약함
- hrtimer_switch_to_hres : jiffies 가 아니라 고해상도 타이머로 전환
9-7 kernel/time/hrtimer.c hrtimer_switch_to_hres
static void hrtimer_switch_to_hres(void)
{
struct hrtimer_cpu_base *base = this_cpu_ptr(&hrtimer_bases);
// tick_nohz_switch_to_nohz 에서도 사용
// 고해상도 사용하는 원샷모드로 변경. (tick_switch_to_oneshot)
if (tick_init_highres()) {
printk(KERN_WARNING "Could not switch to high resolution "
"mode on CPU %d\n", base->cpu);
return;
}
base->hres_active = 1;
hrtimer_resolution = HIGH_RES_NSEC;
tick_setup_sched_timer();
/* "Retrigger" the interrupt to get things going */
retrigger_next_event(NULL);
}
9-8 kernel/time/hrtimer.c hrtimers_init
고해상도 타이머는 ktime 이라는 데이터 유형으로 나노초로 작동.
static int hrtimer_cpu_notify(struct notifier_block *self,
unsigned long action, void *hcpu)
{
int scpu = (long)hcpu;
switch (action) {
case CPU_UP_PREPARE:
case CPU_UP_PREPARE_FROZEN:
init_hrtimers_cpu(scpu);
break;
#ifdef CONFIG_HOTPLUG_CPU
case CPU_DEAD:
case CPU_DEAD_FROZEN:
migrate_hrtimers(scpu);
break;
#endif
default:
break;
}
return NOTIFY_OK;
}
static struct notifier_block hrtimers_nb = {
.notifier_call = hrtimer_cpu_notify,
};
void __init hrtimers_init(void)
{
hrtimer_cpu_notify(&hrtimers_nb, (unsigned long)CPU_UP_PREPARE, // init_hrtimers_cpu 콜백 호출
(void *)(long)smp_processor_id());
register_cpu_notifier(&hrtimers_nb); // cpu notify 체인에 등록, up/down 이벤트 발생 시 초기화 수행
}
스케줄 틱
- 커널이 고해상도 타이머를 사용하면. 클록 이벤트 디바이스가 원샷 모드로 실행됨.
- 스케줄 틱을 고해상도 모드에서 사용하면 주기적으로 확인하지 않고 인터럽트로 처리함. tick_cpu_scched ->sched_timer -> tick_sched_timer
주요 업무
- cpu 가 글로벌 틱을 처리할 수 없다면 대행
- 핸들러가 끝나면, 다음 틱이 발생 할 수 있도록 디바이스를 다시 프로그래밍.
저해상도 모드에서는 주기적인 틱을 사용중일때 jiffies 가 증가함에 따라 do_timer 를 호출했으나, 고해상도에서는 모든 cpu 가 idle 상태이고 전역 틱이 없는 상황이 발생 할 수 있음. 고해상도 모드에서는 tick_sched_timer 에서 주기적인 틱도 담당해줘야함
struct tick_sched {
struct hrtimer sched_timer; // 틱을 구현하는데 사용하는 타이머
unsigned long check_clocks; // 클록 소스에 변화가 있거나 틱 디바이스 모드가 변경될 때 설정
enum tick_nohz_mode nohz_mode; // 작동 모드.
ktime_t last_tick; // 마지막으로 프로그래밍 된 틱 시간 (ns)
int inidle; // nohz idle 진입 여부 확인
int tick_stopped; // nohz 로 틱이 멈춘 상태 확인
unsigned long idle_jiffies;
unsigned long idle_calls;
unsigned long idle_sleeps;
int idle_active;
ktime_t idle_entrytime;
ktime_t idle_waketime;
ktime_t idle_exittime;
ktime_t idle_sleeptime;
ktime_t iowait_sleeptime; // 주기 틱이 마지막으로 비활성화된 시간
ktime_t sleep_length; // 주기 틱이 비활성화된 상태로 유지되는 시간
unsigned long last_jiffies; // 다음 타이머가 만료될 jiffy 값
u64 next_timer;
ktime_t idle_expires; // nohzidle 진입 시 nohz 만료 예정 시각 (ns)
int do_timer_last;
atomic_t tick_dep_mask;
};
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
250412 공통 클록 프레임워크 & 타임 서브시스템 (9.1~9.2) (0) | 2025.04.12 |
---|---|
250405 인터럽트 지연처리 (8.4) (0) | 2025.04.05 |
250329 인터럽트 개념과 핸들러 등록 (8.1~8.3) (0) | 2025.03.29 |
250322 Secondary Booting (7.3) (0) | 2025.03.22 |
250317 SMP 를 위한 커널지원 & cpu topology(7.1~7.2) (0) | 2025.03.17 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- 봄날에 스케치
- boost
- mysql
- Reciprocal n-body Collision Avoidance
- print shared_ptr class member variable
- Visual Studio
- Quest2
- C++
- it's called a vrpit
- 클래스 맴버 변수 출력하기
- chrome-extension
- hole-punching
- 잘못된 빨간줄
- 우리는 vr핏이라고 부릅니다
- Golang
- red underline
- 영상 픽셀화 하기
- ad skip
- 카카오
- vr핏
- SuffixArray
- vrpit
- 에러 위치 찾기
- 면접
- RVO
- Obstacle Avoidance
- 코어 남기기
- shared_from_this
- cockroach db
- set value
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
글 보관함