티스토리 뷰
9 시간과 타이머 관리
리눅스의 시간은 변화를 인식하기 위한 수단으로 타임라인을 구성하는 기준이다.
타이머란 이벤트를 처리할때 프로세스를 제어하거나 측정하는데 사용할 수 있는 특별한 종류의 시계다.
시간과 타이머를 관리하기 위한 서비스
- 시간 관리를 위한 타임 키핑
- 클록의 사용 밀 동기화를 위한 관리
- 시간의 표현
- 타이머 이벤트의 인터럽트 스케줄링
이런 서비스를 위해 중요 기반 자원인 클록을 사용해야 한다.
이 구현이 하드웨어와 연관되어 있어, 추상화를 하기위한 노력이 많이 진행됨.
9장이서는 클록용 프레임워크와 일반 타임 서브 시스템, 타이머 활용을 알아보자.
9.1 공통 클록 프레임워크 (CCF)
SoC(system on chip) 마다 비슷하게 구현되던 API 를 해결하기 위해 커널 3.4 에서 CCF 가 추가되었다.
9.1.1 클록
SoC 의 클록은 트리 형태로 구성된다
- 하위 클록을 활성화 할때 상위 클록도 활성화 되어야 함
- 모든 하위 클록이 비활성화 될 때 상위 클록도 비활성화 되어야 함
- 클록은 자신이 활성화/비활성화 되는 시기를 알아야 함
9.1.2 기본적인 클록의 구성 요소
고정 클록, 클록 게이팅, rate 조정, 멀티 플렉싱 등
디바이스에 따라 클록의 주파수는 가변적으로 필요해져, 이를 위한 클록의 분배는 PLL 을 통해 가변적이거나 고정된 주파수를 발생 시킬 수 있다.
그러면 왜 클록별로 주파수를 다르게 쓸까?
- 주파수를 높이면 더 많이 실행됨. 더 빠른 대신 전력이 많이듬
- 각 주파수 타입별로 다르게 사용하길 원하는걸지도?
그러면 왜 clk_fixed_rate, clk_gate 같은 여러 타입의 클록이 있을까?
- SoC(System on Chip)마다 클럭 하드웨어 구조가 다르기 때문, device 트리에 정리하는듯
9.1.3 CLK 프레임 계층 구현을 위한 인터페이스
struct clk_fixed_rate {
// clk 공용
struct clk_hw hw;
// 각 타입별로 필요한 것
unsigned long fixed_rate;
unsigned long fixed_accuracy;
u8 flags;
};
struct clk_hw {
struct clk_core *core;
struct clk *clk;
const struct clk_init_data *init;
};
struct clk_init_data { // inclide/linux/clk-provider.h
const char *name;
const struct clk_ops *ops;
const char * const *parent_names;
u8 num_parents;
unsigned long flags;
};
클록의 구조와 계층을 표현하기 위해 clk_hw
라는 구조체를 사용한다.
- clk: 클록 토폴리지 (기계 정보)
- clk_ops: 하드웨어 관련 콜백과 특정 콜백을 모델링
- clk_init_data: 클록 초기화에 필요한 인터페이스
clk_ops: clk 하드웨어 지원 인터페이스
- 각 콜백이 정의되어있고, 각 클록 타입별로 필수 처리해야 하는 clk_ops 를 정의한다.
struct clk_ops {
int (*prepare)(struct clk_hw *hw);
void (*unprepare)(struct clk_hw *hw);
int (*is_prepared)(struct clk_hw *hw);
...
}
// 각 타이머에서 콜백 넣을 수 있음. enable, disable 은 clk_gate 같은 타입에서 지정함
struct clkops sdhc_clk_ops = {
.enable = sdhc_clk_enable,
.disable = sdhc_clk_disable,
};
#define APMU_CLK_OPS(_name, _reg, _eval, _rate, _ops) \
struct clk clk_##_name = { \
.clk_rst = APMU_##_reg, \
.enable_val = _eval, \
.rate = _rate, \
.ops = _ops, \
}
구현이 안되어있다면? 기본 값을 사용한다.
static bool clk_core_is_prepared(struct clk_core *core)
{
/*
* .is_prepared is optional for clocks that can prepare
* fall back to software usage counter if it is missing
*/
if (!core->ops->is_prepared)
return core->prepare_count;
return core->ops->is_prepared(core->hw);
}
9.1.4 관련 초기화 함수
9-1 drivers/clk/clk.c of_cli_init
socpll: socpll@17000120 {
compatible = "apm,xgene-socpll-clock";
clocks = <&clk1>, <&clk2>;
};
CLK_OF_DECLARE(xgene_socpll_clock, "apm,xgene-socpll-clock", xgene_socpllclk_init);
struct clock_provider {
of_clk_init_cb_t clk_init_cb;
struct device_node *np;
struct list_head node;
};
static int parent_ready(struct device_node *np)
{
int i = 0;
while (true) {
struct clk *clk = of_clk_get(np, i); // of_clk_get 으로 clk 가져와서 초기화 됐는지 확인함
...
}
static struct clk *__of_clk_get(struct device_node *np, int index,
const char *dev_id, const char *con_id)
{
struct of_phandle_args clkspec;
struct clk *clk;
int rc;
if (index < 0)
return ERR_PTR(-EINVAL);
rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
&clkspec);
...
}
static int __of_parse_phandle_with_args(const struct device_node *np,
const char *list_name,
const char *cells_name,
int cell_count, int index,
struct of_phandle_args *out_args)
void __init of_clk_init(const struct of_device_id *matches)
{
LIST_HEAD(clk_provider_list);
if (!matches)
matches = &__clk_of_table; // device tree 의 compatible 에 정의된걸, CLK_OF_DECLARE 로 등록해둠
for_each_matching_node_and_match(np, matches, &match) { // np = node pointer
struct clock_provider *parent;
if (!of_device_is_available(np))
continue;
// 리스트에 추가
}
while (!list_empty(&clk_provider_list)) {
is_init_done = false;
list_for_each_entry_safe(clk_provider, next, // clk_provider_list 를 순회
&clk_provider_list, node) {
if (force || parent_ready(clk_provider->np)) { // 부모부터 초기화
clk_provider->clk_init_cb(clk_provider->np);
of_clk_set_defaults(clk_provider->np, true); // 기본 설정
list_del(&clk_provider->node);
of_node_put(clk_provider->np);
kfree(clk_provider);
is_init_done = true;
}
}
if (!is_init_done) // 만약에 문제가 있으면 force 로 처리함
force = true;
}
}
9.2 타임 서브시스템
커널에서 주어진 시간에 따른 이벤트를 처리하기 위해 타이머 디바이스를 사용함. 각각의 디바이스는 하나의 이벤트만 프로그램하게 되어 있음.
- 글로벌 클록 디바이스 : 시스템 지표인 jiffies 값을 관리하고 갱신함
- 각 cpu의 로컬 클록 디바이스: 각 cpu 자원 관리와 이벤트 처리. 고해상도 처리 가능
9.2.1 기본 구성 개요
- 클록 소스: 시간 정보를 읽어오기 위한 디바이스.
struct clocksource
- 클록 이벤트 디바이스: 특정 시간에 이벤트를 발생시키는 디바이스
struct clock_event_device
로컬 클록은 cpu 마다 할당되어, 고해상도 타이머를 수행하는데 이를 tick 이라고 부름
- 틱 디바이스: 일정한 시간 간격으로 발생하는 연속 스트림 틱
struct tick_device
9.2.2 클록 소스
리눅스에서 제공하는 시간 관리(timekeeping) 을 위한 추상화 구조.
근데 왜 위의 clk_hw 나 clk_core 같은애랑 따로 관리되는거지? CCF / 타이머 서브 시스템으로 별개로 관리 됨. 이건 device 가 아니라 시스템인걸로 보임
struct clocksource {
cycle_t (*read)(struct clocksource *cs); // 사이클 값 읽는 함수 포인터
cycle_t mask; // 유효 비트 추출하기 위한 클록 마스크
u32 mult; // 주기를 사이클당 나노초로 변환하기 위한 multiple, shift 값
u32 shift;
u64 max_idle_ns;
u32 maxadj;
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
struct arch_clocksource_data archdata;
#endif
u64 max_cycles; // 안전하게 곱할수있는 최대 사이클 값
const char *name;
struct list_head list;
int rating; // ---> 클록 소스의 품질 기준.
int (*enable)(struct clocksource *cs);
void (*disable)(struct clocksource *cs);
unsigned long flags;
void (*suspend)(struct clocksource *cs);
void (*resume)(struct clocksource *cs);
/* private: */
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
/* Watchdog related data, used by the framework */
struct list_head wd_list;
cycle_t cs_last;
cycle_t wd_last;
#endif
struct module *owner;
};
clocksource_jiffies
커널은 부팅시 적절한 클록 소스가 없다면 jiffies 기반의 클록을 제공한다.
틱 인터럽트시 마다 값이 갱신된다.
struct clocksource {
cycle_t (*read)(struct clocksource *cs);
9-2 kernel/time/timekeeping.c timekeeping_init
clocksource 초기화 중 하나의 예시로 timekeeping_init 으로 xtime 구조체가 어떻게 초기화 되는지
static struct {
seqcount_t seq;
struct timekeeper timekeeper;
} tk_core ____cacheline_aligned;
typedef struct seqcount {
unsigned sequence;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} seqcount_t;
struct lockdep_map {
struct lock_class_key *key;
struct lock_class *class_cache[NR_LOCKDEP_CACHING_CLASSES];
const char *name;
#ifdef CONFIG_LOCK_STAT
int cpu;
unsigned long ip;
#endif
};
nsec = 1/100,000,000 초
static void tk_set_xtime(struct timekeeper *tk, const struct timespec64 *ts)
{
tk->xtime_sec = ts->tv_sec;
tk->tkr_mono.xtime_nsec = (u64)ts->tv_nsec << tk->tkr_mono.shift;
}
void __init timekeeping_init(void)
{
struct timekeeper *tk = &tk_core.timekeeper;
read_persistent_clock64(&now); // 현재시간 읽어옴
if (now.tv_sec || now.tv_nsec)
persistent_clock_exists = true;
read_boot_clock64(&boot);
raw_spin_lock_irqsave(&timekeeper_lock, flags);
write_seqcount_begin(&tk_core.seq); // 카운터 1증가와 wmb
ntp_init(); // network time protocol 초기화
clock = clocksource_default_clock(); // 일반적으로 jiffies 기반 클록 소스
if (clock->enable)
clock->enable(clock);
tk_setup_internals(tk, clock); // timekeeping 초기화
tk_set_xtime(tk, &now); // timekeeper 에 현재시간 등록
tk->raw_time.tv_sec = 0;
tk->raw_time.tv_nsec = 0;
if (boot.tv_sec == 0 && boot.tv_nsec == 0)
boot = tk_xtime(tk);
set_normalized_timespec64(&tmp, -boot.tv_sec, -boot.tv_nsec);
tk_set_wall_to_mono(tk, tmp); // wall time 은 현재시간, monotonic 시간은 부팅 후 시간
timekeeping_update(tk, TK_MIRROR | TK_CLOCK_WAS_SET);
write_seqcount_end(&tk_core.seq); // wmb dhk 카운터 1증가
raw_spin_unlock_irqrestore(&timekeeper_lock, flags);
}
9-3 arch/arm64/kernel/time.c time_init
timekeeping 을 수행하기 위한 구조를 아키텍처 측면에서 초기화
하드웨어 클록을 초기화하고, 타이럽트 인터럽트 주기를 설정. 디바이스 트리에 의존적임
static struct hrtimer bctimer;
static struct clock_event_device ce_broadcast_hrtimer = {
.set_state_shutdown = bc_shutdown,
.set_next_ktime = bc_set_next,
.features = CLOCK_EVT_FEAT_ONESHOT |
CLOCK_EVT_FEAT_KTIME |
CLOCK_EVT_FEAT_HRTIMER,
.rating = 0,
.bound_on = -1,
.min_delta_ns = 1,
.max_delta_ns = KTIME_MAX,
.min_delta_ticks = 1,
.max_delta_ticks = ULONG_MAX,
.mult = 1,
.shift = 0,
.cpumask = cpu_all_mask,
};
void __init time_init(void)
{
u32 arch_timer_rate;
of_clk_init(NULL); // 9-1 에서 봤던 device tree clock 초기화
clocksource_probe(); // clock device 의 init_function 호출과 clock sources 카운트 증가
tick_setup_hrtimer_broadcast(); // 커널 내부의 공용 타이머를 초기화 bctimer. 틱마다 bc_hjandler 가 실행됨. 그리고 event_device 를 추가
arch_timer_rate = arch_timer_get_rate();
if (!arch_timer_rate)
panic("Unable to initialise architected timer.\n");
/* Calibrate the delay loop directly */
lpj_fine = arch_timer_rate / HZ;
}
9.2.3 클록 이벤트 (struct clock_event_device)
클록 이벤트는 원하는 시간에 빈트를 발생시키기 위한 타이머 프로그래밍에 사용한다.
- periodic: 규칙적인 주기로 타이머 인터럽트 발생
- oneshot: 일정 시간후 한번 발생
device tree 에 등록된 타이머 하나는 하나의 clock_event_device 를 사용함
struct clock_event_device {
void (*event_handler)(struct clock_event_device *); // 타이머 만료시 호출
int (*set_next_event)(unsigned long evt, struct clock_event_device *); // 다음 이벤트 시간 설정
int (*set_next_ktime)(ktime_t expires, struct clock_event_device *);
ktime_t next_event; // oneshot 모드에서 다음 이벤트가 만료될 ktime 값
u64 max_delta_ns; // 프로그래밍 가능한 최대 시간
u64 min_delta_ns; // 프로그래밍 가능한 최소 시간
u32 mult; // 나노초 승수
u32 shift; // 나노초 사이클 나눔
enum clock_event_state state_use_accessors; // 디바이스의 현재 상태
unsigned int features; // 기능 플래그
unsigned long retries; // 재시도 횟수
int (*set_state_periodic)(struct clock_event_device *); // 상태를 periodic 으로 변경
int (*set_state_oneshot)(struct clock_event_device *); // 상태를 oneshot 으로 변경
int (*set_state_oneshot_stopped)(struct clock_event_device *); // 상태를 oneshot_stopped 로 변경
int (*set_state_shutdown)(struct clock_event_device *); // 상태를 종료료 변경
int (*tick_resume)(struct clock_event_device *); // clock_event 디바이스를 다시 시작할때 사용
void (*broadcast)(const struct cpumask *mask); // 이벤트를 브로드캐스트하는 기능
void (*suspend)(struct clock_event_device *);
void (*resume)(struct clock_event_device *);
unsigned long min_delta_ticks; // 최소 틱값
unsigned long max_delta_ticks; // 최대 틱 값
const char *name; // 클록 이벤트 이름
int rating; // 클록 이벤트 디바이스 rate 값
int irq; // irq 번호 (cpu 로컬 디바이스 아닌 경우)
int bound_on; // 바인딩된 cpu 번호
const struct cpumask *cpumask; // 타이머가 동작하는 cpu mask
struct list_head list; // 이벤트 디바이스 리스트에 연결하는 노드
struct module *owner;
} ____cacheline_aligned;
- mult 와 shift
time(시간) = cycle(주기) / frequency(주파수)
시스템에서 나누기를 사용하지 않음
그래서 주기를 ns 로 변경할때 사용
HZ: 1초동안 n번
#define NSEC_PER_JIFFY ((NSEC_PER_SEC+HZ/2)/HZ) -> 1틱을 수행하는데 걸리는 NS
.mult = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
.shift = JIFFIES_SHIFT,
static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)
{
return ((u64) cycles * mult) >> shift;
}
9.2.4 틱 디바이스
스케줄 틱 발생 담당
kernel/time/tich-sched.h
enum tick_device_mode {
TICKDEV_MODE_PERIODIC,
TICKDEV_MODE_ONESHOT,
};
struct tick_device {
struct clock_event_device *evtdev;
enum tick_device_mode mode;
};
include/time/tick-internal.h
DECLARE_PER_CPU(struct tick_device, tick_cpu_device); // 각 cpu 별 tick device
extern ktime_t tick_next_period; // 다음 글로벌 틱 이벤트 발생할 시간
extern ktime_t tick_period; // 틱 사이의 간격
extern int tick_do_timer_cpu __read_mostly; // cpu 번호
틱 디바이스의 운영
static void tick_setup_device(struct tick_device *td,
struct clock_event_device *newdev, int cpu,
const struct cpumask *cpumask)
{
/*
* First device setup ?
*/
if (!td->evtdev) {
// 글로벌 틱 디바이스가 선택되지 않았다면, 이 타이머를 글로벌 틱 담당 디바이스로 설정
} else {
handler = td->evtdev->event_handler;
next_event = td->evtdev->next_event;
td->evtdev->event_handler = clockevents_handle_noop;
}
/*
* When the device is not per cpu, pin the interrupt to the
* current cpu:
*/
if (!cpumask_equal(newdev->cpumask, cpumask))
irq_set_affinity(newdev->irq, cpumask);
// 브로드캐스팅이 활성화 되면, 틱을 만들어 줄 필요 없음.
// 브로드캐스트 모드는 여러 CPU가 동기화된 시간 틱을 공유하는 모드
if (tick_device_uses_broadcast(newdev, cpu))
return;
if (td->mode == TICKDEV_MODE_PERIODIC)
tick_setup_periodic(newdev, 0);
else
tick_setup_oneshot(newdev, handler, next_event);
}
틱 브로드캐스팅과 다이나믹틱
리눅스는 틱 인터럽트를 관리하는 방법으로 틱 브로드캐스팅과 다이나믹 틱을 제공한다.
- 틱 브로드캐스팅: 공유 타이머를 사용해 한 번의 틱으로 여러 CPU가 깨우는 방식, 틱 발생을 줄임.
- 다이나믹 틱: CPU가 유휴 상태일 때 틱 인터럽트를 생략하거나 줄여 전력 소모를 절약하는 방식.
상호 배타적으로 사용되는 듯 하나 아직은 잘 모르겠음
kkernel/time/tick-common.c tick_init
틱 브로드캐시틍, 다이나믹틱 초기화
void __init tick_init(void) {
tick_broadcast_init(); // 틱 브로드 캐스팅 초기화
tick_nohz_init(); // 다이나믹 틱 초기화
}
void __init tick_broadcast_init(void)
{
zalloc_cpumask_var(&tick_broadcast_mask, GFP_NOWAIT); // broadcasting 이 필요하다고 요청온 cpu mask
zalloc_cpumask_var(&tick_broadcast_on, GFP_NOWAIT); // broadcasting 중인 cpu
zalloc_cpumask_var(&tmpmask, GFP_NOWAIT); // 작업중 쓰는 cpu mask
#ifdef CONFIG_TICK_ONESHOT
zalloc_cpumask_var(&tick_broadcast_oneshot_mask, GFP_NOWAIT); // 원샷 모드에서 브로드캐스트가 항상 필요한 CPU(로컬타이머가 없음)
zalloc_cpumask_var(&tick_broadcast_pending_mask, GFP_NOWAIT); // 지금 브로드캐스트 이벤트를 기다리는 중인 CPU(일시적)
zalloc_cpumask_var(&tick_broadcast_force_mask, GFP_NOWAIT); // 강제로 브로드캐스트를 받아야 하는 CPU, 예: 로컬 타이머 완전 비활성화
#endif
}
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
250419 9.3 타이머 관리 [완] (0) | 2025.04.19 |
---|---|
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
- 에러 위치 찾기
- chrome-extension
- vrpit
- 코어 남기기
- boost
- print shared_ptr class member variable
- it's called a vrpit
- Quest2
- SuffixArray
- Visual Studio
- ad skip
- RVO
- 우리는 vr핏이라고 부릅니다
- 봄날에 스케치
- red underline
- vr핏
- 카카오
- 영상 픽셀화 하기
- mysql
- 면접
- shared_from_this
- C++
- hole-punching
- cockroach db
- Golang
- Obstacle Avoidance
- Reciprocal n-body Collision Avoidance
- 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 |