<kernel v5.4>
Tick Device Subsystem
Tick Device Subsystem은 CONFIG_HZ 주기에 해당하는 타이머 스케줄 틱을 발생시키고, 이 스케줄 틱을 이용하는 다음과 같은 여러 루틴들을 처리한다.
- jiffies
- jiffies 값을 증가시킨다. (jiffies 담당 cpu가 처리하며 nohz를 위해 담당 cpu는 변경될 수 있다.)
- timer
- lowres timer wheel을 검색하여 만료된 타이머의 함수를 호출한다.
- 스케줄러 틱
- 런큐의 로드 값을 갱신하고, 현재 동작 중인 태스크 스케줄러(cfs, rt, deadline, …)의 (*task_tick)을 호출한다.
- rcu
- rcu core를 처리한다.
- process account
- cpu 사용 시간을 계량한다.
다음 그림은 generic timer sybsystem을 모두 보여준다.
틱 디바이스의 모드 운영
주기적으로 틱을 발생하기 위해 다음과 같이 두 가지 모드를 사용한다.
- hz 기반의 periodic 모드로 운영
- periodic 또는 oneshot 기능을 가진 클럭 이벤트 디바이스 모두 동작할 수 있다.
- legacy 하드웨어에서는 low-resolution 타이머가 oneshot을 지원하지 않고 지속적(periodic)으로 틱을 만드는 hw를 호환하여 운영하기 위해 커널은 처음 부트업시 항상 이 모드로 시작한다.
- nohz 기반의 oneshot 모드로 운영
- oneshot 기능을 가진 high-resolution 타이머를 가진 클럭 이벤트 디바이스만 운영 가능하다.
- 틱 모드는 처음에 periodic으로 운영하다가 hrtimer가 준비된 후에 틱 디바이스의 모드를 oneshot 모드로 변경하여 운영한다. 이 과정에서 tick 핸들러가 다음과 같은 순으로 바뀐다.
- tick_handle_periodic() -> clockevents_handle_noop() -> tick_sched_timer()
다음 그림은 tick devices subsystem이 동작하는 과정을 보여준다.
Per-cpu Tick Device 및 Tick Broadcast Device
틱을 관리하는 드라이버들로 클럭 이벤트 디바이스의 기능에 따라 각 cpu들은 틱 cpu 디바이스 또는 틱 브로드캐스트 디바이스 둘 중 하나에 연결하여 사용한다.
- Per-cpu Tick 디바이스 (전역 변수 per-cpu tick_cpu_device)
- per-cpu 타이머, 절전기능(c3stop) 타이머와 dummy 타이머를 사용한 클럭 이벤트 디바이스들로 사용 가능하다.
- Tick 브로드캐스트 디바이스 (전역 변수 tick_broadcast_device)
- per-cpu 타이머, 절전기능(c3stop) 타이머 및 dummy 타이머를 사용하지 않는 클럭 이벤트 디바이스만 사용 가능하다.
- 틱 브로드캐스트 모드가 oneshot인 경우 클럭 이벤트 디바이스도 oneshot 기능이 준비되어 있어야 한다.
- 시스템에서 nohz 구현을 위해 Tick 브로드캐스트 디바이스를 사용하지 않는 경우도 있다. (rpi2, rpi3도 사용하지 않는다.)
c3stop (절전 상태 진입하여 코어, IC 및 타이머 등 파워 다운)
arm 아키텍처에 내장된 타이머 중 CP15 레지스터를 통해 제어되는 경우 cpu가 c3(deep-sleep) 절전 상태로 진입하면 절전을 위해 코어, IC 및 타이머의 power를 다운시킨다. 이렇게 타이머 전원을 다운시키면 해당 타이머에 의해 틱 처리를 하기 위해 스스로 wake-up할 수 없으므로 다른 깨어있는 cpu의 도움을 받아야 한다. arm 아키텍처의 내장 타이머는 기본적으로 c3stop을 지원하는데 cpu가 c3(deep-sleep) 상태로 진입할 때 최소 한 개의 cpu는 지속적으로 tick을 처리하고 c3 상태에 진입한 cpu를 대신해서 틱의 만료시간을 계산하여 알릴 필요가 있다. 이렇게 만료된 cpu를 깨워 틱을 처리하게 하기 위해 틱 브로드캐스트 디바이스를 사용한다.
- 현재까지 armv7, armv8 아키텍처의 대부분은 core 단위의 idle에서는 wfi를 사용하여 절전활동을 하고, 클러스터에 소속한 모든 core가 idle되는 경우 deep-sleep 상태로 진입한다. suspend와 동일하게 클러스터 내의 파워를 끄는 형태인데 이 때 시스템에 따라 해당 클러스터의 타이머 및 인터럽트 컨트롤러도 같이 전원이 꺼질 수 있다.
- arm 아키텍처에 내장된 CP15로 제어되는 타이머라 하더라도 특정 시스템은 절전 설계되지 않아 항상 타이머 전원이 on되어 있다. 이러한 경우 c3stop을 false(“always-on”)로 설정하여 내부적으로 스케줄 틱의 관리에 틱 브로드캐스트 디바이스를 사용할 필요 없이 그냥 틱 cpu 디바이스를 사용한다.
c3stop 기능을 사용하는 클럭소스
- x86의 LAPIC 타이머
- ARM 내장 generic 타이머
- arm_arch_timer
- 기타
- mips-gic-timer
- clps711x-timer
절전을 위한 전원 관리 시스템이 없는 SoC에서는 c3stop 기능을 사용하면 안된다. 이러한 시스템에서 절전을 위해 타이머의 전원을 off 후 on 하는 경우 타이머의 comparison 레지스터의 내용이 유실되는 현상이 벌어진다. 이러한 증상으로 인해 리눅스 커널은 디바이스 트리 스크립트의 타이머 노드에 “always-on;” 속성을 추가하여 c3stop 기능을 사용하지 못하게 막는다. (항상 전원이 켜져서 동작하는 타이머로 인식한다.)
- rpi2, rpi3: “always-on;” 속성을 사용하여 브로드캐스트 기능을 사용하지 못하게 한다.
틱 브로드캐스트
절전을 위해 tick을 발생시키지 않는 nohz-idle을 구현할 때 cpu가 c3(deep-sleep) 상태에 진입되면 타이머 전원도 같이 꺼지는 시스템을 위해 다음과 같은 브로드캐스트 구현이 필요하다.
- cpu가 처리할 일이 없어 idle 상태로 진입하기 전에 브로드캐스트를 수신하기 위해 해당 cpu의 비트를 설정한다.
- 브로드캐스트 디바이스의 모드가 periodic인 경우 shutdown 한다.
- 모든 cpu가 처리할 일이 없어도 최소 하나의 대표 cpu는 브로드캐스트 디바이스가 아니라 틱 디바이스로 동작해야 한다.
- 대표 cpu는 c3(deep-sleep) 상태의 cpu를 깨우기 위해 broadcast를 한다.
- c3 상태에 있는 cpu를 깨울 때 IPI(Inter Process Interrupt)를 통해 브로드캐스트하여 해당 cpu를 깨운다.
- 깨어난 cpu는 더 이상 브로드캐스트를 수신하지 않아도 되므로 해당 cpu의 비트를 클리어한다.
- 브로드캐스트 디바이스의 모드가 periodic인 경우 다시 프로그램한다.
C-State
인텔에서 정의한 절전 상태 (참고: Everything You Need to Know About the CPU C-States Power Saving Modes
- C0
- 풀 파워로 인스트럭션을 수행한다.
- C1
- 내부 코어 클럭을 정지시킨 halt 상태
- 외부 클럭과 ACPI는 동작 중으로 인터럽트는 처리할 수 있는 상태
- C2
- Stop-Grant & Stop-Clock 상태로 내부 및 외부 클럭이 정지된 상태
- ACPI는 동작중으로 인터럽트는 처리할 수 있는 상태
- C3
- 플러시 캐시. 내부 클럭 및 외부 클럭도 받아들이지 않고 deep sleep 상태
- ACPI를 통한 인터럽트도 받아들이지 않으며, 특별히 wake-up을 통한 장치를 통해서만 깨울 수 있다.
- c3stop이라고도 한다.
- C4/C5
- 인텔 듀오프로세서에서 멀티 코어 전체가 deep sleep 상태
- C6
- 인텔 Core i7에서 모든 코어가 좀 더 deeper sleep 상태
다음 그림은 tick 핸들러가 처음 설정되는 모습을 보여준다.
- arm 아키텍처에 내장된 타이머는 부트업 시 틱 periodic 모드로 출발하므로 tick_handle_periodic() 핸들러가 선택된다.
다음 그림은 tick_periodic_handler()가 호출된 후 high-resolution 타이머가 준비되고, nohz oneshot 모드로 전환되어 hrtimer_interrupt() 핸들러가 선택된다. hrtimer_intrerrupt() 내부에서 스케줄 틱 타이머 함수인 tick_sched_timer() 함수가 호출된다.
다음 그림은 틱 cpu 디바이스 및 틱 브로드캐스트 디바이스가 사용하는 틱 핸들러들이다.
- hz/nohz 기반의 high resolution을 사용하는 핸들러는 hrtimer_interrupt이지만 틱 디바이스와 별개로 모든 hrtimer의 만료 시간을 관리한다. 따라서 틱 디바이스만을 대상으로 좁히는 경우 실제 틱 처리에 대한 핸들러는 tick_sched_timer()라고 할 수 있다.
틱 cpu 디바이스 또는 틱 브로드캐스트 디바이스 등록
tick_check_new_device()
kernel/time/tick-common.c
/* * Check, if the new registered device should be used. Called with * clockevents_lock held and interrupts disabled. */
void tick_check_new_device(struct clock_event_device *newdev)
{
struct clock_event_device *curdev;
struct tick_device *td;
int cpu;
cpu = smp_processor_id();
td = &per_cpu(tick_cpu_device, cpu);
curdev = td->evtdev;
/* cpu local device ? */
if (!tick_check_percpu(curdev, newdev, cpu))
goto out_bc;
/* Preference decision */
if (!tick_check_preferred(curdev, newdev))
goto out_bc;
if (!try_module_get(newdev->owner))
return;
/*
* Replace the eventually existing device by the new
* device. If the current device is the broadcast device, do
* not give it back to the clockevents layer !
*/
if (tick_is_broadcast_device(curdev)) {
clockevents_shutdown(curdev);
curdev = NULL;
}
clockevents_exchange_device(curdev, newdev);
tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
tick_oneshot_notify();
return;
out_bc:
/*
* Can the new device be used as a broadcast device ?
*/
tick_install_broadcast_device(newdev);
}
현재 cpu의 tick 디바이스로 기존 tick 디바이스보다 새 tick 디바이스가 더 좋은 rating 등급인 경우 변경하여 사용할지 체크한다.
- 처음 호출 시에는 요청 clock event 디바이스가 tick 디바이스로 사용된다.
- nohz 구현을 위해 경우에 따라 등록되는 클럭 이벤트 디바이스가 틱 브로드캐스트 디바이스로 동작할 수도 있다.
- 코드 라인 12~13에서 현재 cpu의 틱 디바이스에 사용중인 clock event 디바이스를 새 디바이스로 변경 가능한지 체크한다. 사용할 수 없으면 out_bc 레이블로 이동한다.
- 코드 라인 16~17에서 기존 디바이스와 새 디바이스를 비교하여 새 디바이스의 rating이 더 높은 경우가 아니면 out_bc 레이블로 이동한다.
- 코드 라인 19~20에서 새 clock event 디바이스 모듈에 대한 참조 카운터를 증가시킨다.
- 코드 라인 27~30에서 기존 디바이스가 브로드 캐스트 디바이스인 경우 shutdown 시킨다.
- 코드 라인 31~32에서 틱 디바이스에 사용할 새 클럭 이벤트 디바이스로 변경한다.
- 코드 라인 33~35에서 새 디바이스에 oneshot 기능이 있는 경우 클럭 이벤트 디바이스가 변경되었음을 틱 스케쥴 정보에 async하게 알리고 함수를 빠져나간다.
- 코드 라인 37~41에서 out_bc 레이블이다. 새 클럭 이벤트 디바이스를 체크하여 브로드 캐스트 디바이스로 지정한다.
다음 그림은 각 cpu별로 여러 개의 틱 이벤트 디바이스가 존재하는 경우 best rating 디바이스를 선택하고 그 나머지들은 release 리스트에 두는 모습을 보여준다.
틱 디바이스 조건 체크
tick_check_percpu()
kernel/time/tick-common.c
static bool tick_check_percpu(struct clock_event_device *curdev,
struct clock_event_device *newdev, int cpu)
{
if (!cpumask_test_cpu(cpu, newdev->cpumask))
return false;
if (cpumask_equal(newdev->cpumask, cpumask_of(cpu)))
return true;
/* Check if irq affinity can be set */
if (newdev->irq >= 0 && !irq_can_set_affinity(newdev->irq))
return false;
/* Prefer an existing cpu local device */
if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
return false;
return true;
}
새 클럭 이벤트 디바이스를 tick 디바이스로 설정할 수 있는지 여부를 반환한다.
- 코드 라인 4~5에서 요청 cpu가 새 클럭 이벤트 디바이스가 허용하는 cpu가 아닌 경우 false를 반환한다.
- 코드 라인 6~7에서 요청 cpu가 새 디바이스에 per-cpu로 등록된 경우 true를 반환한다.
- 코드 라인 9~10에서 새 디바이스가 취급하는 irq로 affinity 설정할 수 없으면 false를 반환한다.
- FEAT_DYNTICK이 부여된 드라이버들
- armv7 아키텍처에 내장된 generic 타이머의 메모리 mapped 드라이버 – “arm,armv7-timer-mem”
- “st,nomadik-mtu” 드라이버
- FEAT_DYNTICK이 부여된 드라이버들
- 코드 라인 12~13에서 요청 cpu가 현재 디바이스에 이미 per-cpu로 등록되어 있는 경우 false를 반환한다.
tick_check_preferred()
kernel/time/tick-common.c
static bool tick_check_preferred(struct clock_event_device *curdev,
struct clock_event_device *newdev)
{
/* Prefer oneshot capable device */
if (!(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) {
if (curdev && (curdev->features & CLOCK_EVT_FEAT_ONESHOT))
return false;
if (tick_oneshot_mode_active())
return false;
}
/*
* Use the higher rated one, but prefer a CPU local device with a lower
* rating than a non-CPU local device
*/
return !curdev ||
newdev->rating > curdev->rating ||
!cpumask_equal(curdev->cpumask, newdev->cpumask);
}
기존 틱 디바이스와 새 디바이스를 비교하여 새 디바이스의 동작 모드 및 등급(rating)이 더 높은 경우 true를 반환한다. (oneshot -> rating 순)
- 코드 라인 5~7에서 기존 틱 디바이스는 oneshot 기능이 있는데 새 디바이스가 oneshot 기능이 없는 경우 false를 반환한다. (oneshot 우선)
- 코드 라인 8~9에서 기존 틱 디바이스가 이미 oneshot 모드로 동작하는데 새 디바이스가 oneshot 기능이 없는 경우 false를 반환한다. (oneshot 우선)
- 코드 라인 16~18에서 새 디바이스의 rating이 더 높은 경우 true를 반환한다.
다음 그림은 새 클럭 이벤트 디바이스가 기존보다 더 구형(periodic) 모드로 동작하거나 rating이 더 낮을 때 선호되지 않는 케이스들을 보여준다.
- 성공 case:
- 기존 틱 디바이스로 등록된 클럭 이벤트 디바이스가 하나도 없는 경우
- 기존 틱 디바이스에 등록된 클럭 이벤트 디바이스의 등급(rating)보다 높고 feature도 서로 같거나 더 높은 oneshot인 경우
tick_oneshot_mode_active()
kernel/time/tick-oneshot.c
/** * tick_check_oneshot_mode - check whether the system is in oneshot mode * * returns 1 when either nohz or highres are enabled. otherwise 0. */
int tick_oneshot_mode_active(void)
{
unsigned long flags;
int ret;
local_irq_save(flags);
ret = __this_cpu_read(tick_cpu_device.mode) == TICKDEV_MODE_ONESHOT;
local_irq_restore(flags);
return ret;
}
현재 cpu의 tick 디바이스가 oneshot 모드인지 여부를 반환한다.
Tick 디바이스로 지정
tick_setup_device()
kernel/time/tick-common.c
/* * Setup the tick device */
static void tick_setup_device(struct tick_device *td,
struct clock_event_device *newdev, int cpu,
const struct cpumask *cpumask)
{
void (*handler)(struct clock_event_device *) = NULL;
ktime_t next_event = 0;
/*
* First device setup ?
*/
if (!td->evtdev) {
/*
* If no cpu took the do_timer update, assign it to
* this cpu:
*/
if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT) {
tick_do_timer_cpu = cpu;
tick_next_period = ktime_get();
tick_period = NSEC_PER_SEC / HZ;
#ifdef CONFIG_NO_HZ_FULL
/*
* The boot CPU may be nohz_full, in which case set
* tick_do_timer_boot_cpu so the first housekeeping
* secondary that comes up will take do_timer from
* us.
*/
if (tick_nohz_full_cpu(cpu))
tick_do_timer_boot_cpu = cpu;
} else if (tick_do_timer_boot_cpu != -1 &&
!tick_nohz_full_cpu(cpu)) {
tick_take_do_timer_from_boot();
tick_do_timer_boot_cpu = -1;
WARN_ON(tick_do_timer_cpu != cpu);
#endif
}
/*
* Startup in periodic mode first.
*/
td->mode = TICKDEV_MODE_PERIODIC;
} else {
handler = td->evtdev->event_handler;
next_event = td->evtdev->next_event;
td->evtdev->event_handler = clockevents_handle_noop;
}
td->evtdev = newdev;
/*
* 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);
/*
* When global broadcasting is active, check if the current
* device is registered as a placeholder for broadcast mode.
* This allows us to handle this x86 misfeature in a generic
* way. This function also returns !=0 when we keep the
* current active broadcast state for this 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에 대해 처음 틱 디바이스가 지정된 경우 틱 디바이스의 모드를 periodic으로 시작한다. (hrtimer가 초기화되어 사용되기 전에는 oneshot 모드로 바꾸지 않는다.)
- 코드 라인 11~42에서 현재 cpu에 대해 tick 디바이스의 클럭 이벤트 디바이스가 설정되지 않은 경우 모드를 periodic으로 설정한다. 그리고 모든 cpu를 대상으로 가장 처음 틱 디바이스가 설정되면 틱에 대한 주기 등 관련 값들을 설정한다. (tick_do_timer_cpu, tick_next_period, tick_period)
- tick_do_timer_cpu는 디폴트 값으로 TICK_DO_TIMER_BOOT 값으로 설정되어 cpu가 아직 지정되지 않았음을 의미한다.
- 코드 라인 43~47에서 tick 디바이스의 클럭 이벤트 디바이스가 이미 설정되어 있지만 새로운 요청이 있는 경우 틱 이벤트 핸들러에 빈 함수를 지정한 후 기존 이벤트 핸들러에서 사용 중인 핸들러를 사용할 준비를 한다.
- 해당 cpu에 대한 틱 디바이스의 클럭 이벤트 디바이스가 변경되는 과정에서 이벤트 핸들러가 동작되어야 할 때 자연스럽게 아무것도 처리하지 않게 한다.
- 코드 라인 49에서 tick 디바이스에 클럭 이벤트 디바이스를 연결한다.
- 코드 라인 55~56에서 새 클럭 이벤트 디바이스가 cpu 내장형(per-cpu)이 아니면 현재 cpu에서 새 틱 디바이스의 irq를 처리할 수 있도록 affinity 설정을 한다.
- FEAT_IRQDYN 기능이 있는 클럭 이벤트 디바이스만이 irq를 요청한 cpu로 연결할 수 있다.
- 브로드캐스트 목적의 인터럽트가 특정 cpu에 고정되지 않고 cpu를 선택(set_irq_affinity)하여 사용할 수 있으며 다음 드라이버에서 사용되고 있다.
- armv7 또는 armv8 아키텍처에 내장된 generic 타이머의 메모리 mapped 드라이버 – “arm,armv7-timer-mem”
- “st,nomadik-mtu”
- 코드 라인 65~66에서 새 클럭 이벤트 디바이스가 브로드캐스트 디바이스로 동작하는 경우 처리가 완료되었으므로 함수를 빠져나간다.
- 코드 라인 68~71에서 tick 디바이스의 모드에 맞게 틱 디바이스를 periodic 및 oneshot 모드로 설정한다.
tick_nohz_full_cpu()
include/linux/tick.h
static inline bool tick_nohz_full_cpu(int cpu)
{
if (!tick_nohz_full_enabled())
return false;
return cpumask_test_cpu(cpu, tick_nohz_full_mask);
}
현재 cpu가 nohz full로 운영되는지 여부를 반환한다.
tick_nohz_full_enabled()
include/linux/tick.h
static inline bool tick_nohz_full_enabled(void)
{
if (!context_tracking_is_enabled())
return false;
return tick_nohz_full_running;
}
nohz full로 운영되는지 여부를 반환한다.
틱 디바이스를 브로드캐스트 디바이스로 등록
tick_device_uses_broadcast()
kernel/time/tick-broadcast.c
/* * Check, if the device is disfunctional and a place holder, which * needs to be handled by the broadcast device. */
int tick_device_uses_broadcast(struct clock_event_device *dev, int cpu)
{
struct clock_event_device *bc = tick_broadcast_device.evtdev;
unsigned long flags;
int ret = 0;
raw_spin_lock_irqsave(&tick_broadcast_lock, flags);
/*
* Devices might be registered with both periodic and oneshot
* mode disabled. This signals, that the device needs to be
* operated from the broadcast device and is a placeholder for
* the cpu local device.
*/
if (!tick_device_is_functional(dev)) {
dev->event_handler = tick_handle_periodic;
tick_device_setup_broadcast_func(dev);
cpumask_set_cpu(cpu, tick_broadcast_mask);
if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)
tick_broadcast_start_periodic(bc);
else
tick_broadcast_setup_oneshot(bc);
ret = 1;
} else {
/*
* Clear the broadcast bit for this cpu if the
* device is not power state affected.
*/
if (!(dev->features & CLOCK_EVT_FEAT_C3STOP))
cpumask_clear_cpu(cpu, tick_broadcast_mask);
else
tick_device_setup_broadcast_func(dev);
/*
* Clear the broadcast bit if the CPU is not in
* periodic broadcast on state.
*/
if (!cpumask_test_cpu(cpu, tick_broadcast_on))
cpumask_clear_cpu(cpu, tick_broadcast_mask);
틱 디바이스 상태에 따라 브로드캐스트 디바이스로 동작시킬지 아닐지 여부를 다음과 같이 결정한다.
- 더미 틱 디바이스인 경우 현재 cpu에 대해 브로드캐스트 디바이스로 동작시킨다. 결과=1
- oneshot 틱 디바이스인 경우 현재 cpu를 브로드캐스트에서 제외한다. 결과=0
- periodic 모드인 경우 브로드캐스트할 cpu가 없는 경우 shutdown한다. 결과=현재 cpu가 브로드캐스트에 포함되어 있는지 여부
- 코드 라인 15~23에서 클럭 이벤트 디바이스가 periodic 및 oneshot 기능이 없는 더미인 경우 핸들러 함수와 브로드캐스트 함수를 설정한다. 그리고 현재 cpu를 tick_broadcast_mask에 추가한다.
- 틱 브로드캐스트 디바이스가 더미 디바이스 상태에 있을 때에도 핸들러 함수가 정상적으로 호출되도록 설정한다.
- 더미 디바이스를 틱 브로드캐스트 디바이스로 사용하고 결과로 1을 반환한다. 모드가 periodic을 지원하면 periodic 모드로 동작시키고 그렇지 않으면 oneshot 모드로 동작시킨다.
- 코드 라인 24~32에서 절전을 위한 c3stop 기능이 없는 경우 tick_broadcast_mask에서 요청 cpu를 제외시킨다. 반대로 c3stop 기능이 있는 경우 현재 디바이스에 브로드캐스트 함수를 설정한다.
- c3stop 기능이 없으면 타이머 전원을 끄지 않고 계속 동작시킨다.
- 코드 라인 38~39에서 periodic용 tick_broadcast_on에 현재 cpu가 포함되지 않은 경우 tick_broadcast_mask에서 현재 cpu를 클리어한다.
switch (tick_broadcast_device.mode) {
case TICKDEV_MODE_ONESHOT:
/*
* If the system is in oneshot mode we can
* unconditionally clear the oneshot mask bit,
* because the CPU is running and therefore
* not in an idle state which causes the power
* state affected device to stop. Let the
* caller initialize the device.
*/
tick_broadcast_clear_oneshot(cpu);
ret = 0;
break;
case TICKDEV_MODE_PERIODIC:
/*
* If the system is in periodic mode, check
* whether the broadcast device can be
* switched off now.
*/
if (cpumask_empty(tick_broadcast_mask) && bc)
clockevents_shutdown(bc);
/*
* If we kept the cpu in the broadcast mask,
* tell the caller to leave the per cpu device
* in shutdown state. The periodic interrupt
* is delivered by the broadcast device, if
* the broadcast device exists and is not
* hrtimer based.
*/
if (bc && !(bc->features & CLOCK_EVT_FEAT_HRTIMER))
ret = cpumask_test_cpu(cpu, tick_broadcast_mask);
break;
default:
break;
}
}
raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
return ret;
}
- 코드 라인 1~13에서 틱 브로드캐스트 디바이스가 oneshot 모드로 동작하는 경우 요청 cpu를 브로드캐스트 대상에서 제외하고 결과로 0을 반환한다.
- tick_broadcast_oneshot_mask와 tick_broadcast_pending_mask에서 요청 cpu를 제외시킨다.
- 코드 라인 15~33에서 틱 브로드캐스트 디바이스가 periodic 모드로 동작하는 경우 요청 cpu가 브로드캐스트 대상에 포함되어 있는지 여부를 반환한다. 만일 브로드캐스트 대상이 없는 경우 디바이스를 shutdown 한다.
tick_device_is_functional()
kernel/time/tick-internal.h
/*
* Check, if the device is functional or a dummy for broadcast
*/
static inline int tick_device_is_functional(struct clock_event_device *dev)
{
return !(dev->features & CLOCK_EVT_FEAT_DUMMY);
}
클럭 이벤트 디바이스가 periodic 및 oneshot 기능 중 하나가 있는 경우 true를 반환한다.
- dummy 디바이스는 periodic 및 oneshot 기능 모두 없다.
tick_device_setup_broadcast_func()
kernel/time/tick-broadcast.c
static void tick_device_setup_broadcast_func(struct clock_event_device *dev)
{
if (!dev->broadcast)
dev->broadcast = tick_broadcast;
if (!dev->broadcast) {
pr_warn_once("%s depends on broadcast, but no broadcast function available\n",
dev->name);
dev->broadcast = err_broadcast;
}
}
틱 디바이스에 브로드캐스트 함수가 설정되지 않은 경우 브로드캐스트 함수를 설정한다.
- 틱 브로드캐스트 함수에서 대상 cpu로 IPI(Inter Process Interrupt)를 발생시킨다.
tick_broadcast()
arch/arm/kernel/smp.c
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
void tick_broadcast(const struct cpumask *mask)
{
smp_cross_call(mask, IPI_TIMER);
}
#endif
대상 cpu들을 IPI(Inter Process Interrupt)로 깨워 IPI _TIMER 기능에 대한 처리를 수행하게 요청한다.
tick_broadcast_start_periodic()
kernel/time/tick-broadcast.c
/*
* Start the device in periodic mode
*/
static void tick_broadcast_start_periodic(struct clock_event_device *bc)
{
if (bc)
tick_setup_periodic(bc, 1);
}
요청한 클럭 이벤트 디바이스를 periodic 모드의 브로드캐스트 디바이스로 사용하여 tick을 발생시킨다.
틱 디바이스 모드 설정
틱 디바이스를 periodic 모드로 설정
tick_setup_periodic()
/* * Setup the device for a periodic tick */
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
tick_set_periodic_handler(dev, broadcast);
/* Broadcast setup ? */
if (!tick_device_is_functional(dev))
return;
if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
!tick_broadcast_oneshot_active()) {
clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);
} else {
unsigned long seq;
ktime_t next;
do {
seq = read_seqbegin(&jiffies_lock);
next = tick_next_period;
} while (read_seqretry(&jiffies_lock, seq));
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
for (;;) {
if (!clockevents_program_event(dev, next, false))
return;
next = ktime_add(next, tick_period);
}
}
}
요청한 클럭 이벤트 디바이스를 periodic 모드의 틱 디바이스로 사용하여 tick을 발생시킨다. broadcast=1로 요청한 경우 브로드캐스트 핸들러를 준비한다.
- 코드 라인 3에서 periodic 핸들러 함수를 지정한다. @broadcast가 요청된 경우 broadcast용 periodic 핸들러 함수를 지정한다.
- 코드 라인 6~7에서 클럭 이벤트 디바이스가 dummy 디바이스인 경우 이미 브로드 캐스트 디바이스이므로 함수를 빠져나간다.
- 코드 라인 9~11에서 클럭 이벤트 디바이스 기능에 periodic이 있고 틱 브로드캐스트 디바이스가 oneshot 모드가 아닌 경우 클럭 이벤트 디바이스를 periodic로 설정한다.
- 코드 라인 12~21에서 1 tick을 더해 다음 만료 시간을 지정한 후 클럭 이벤트 디바이스를 oneshot 모드로 설정한다.
- 코드 라인 23~27에서 클럭 이벤트 디바이스에 프로그램하여 성공하면 함수를 빠져나간다. 만일 실패하는 경우 1 tick 만큼 만료 시간을 더해 성공할 때까지 루프를 돌며 시도한다.
tick_set_periodic_handler()
kernel/time/tick-broadcast.c
/* * Set the periodic handler depending on broadcast on/off */
void tick_set_periodic_handler(struct clock_event_device *dev, int broadcast)
{
if (!broadcast)
dev->event_handler = tick_handle_periodic;
else
dev->event_handler = tick_handle_periodic_broadcast;
}
periodic 핸들러 함수를 지정한다. @broadcast가 요청된 경우 broadcast용 periodic 핸들러 함수를 지정한다.
틱 디바이스를 oneshot 모드로 설정
tick_setup_oneshot()
kernel/time/tick-oneshot.c
/** * tick_setup_oneshot - setup the event device for oneshot mode (hres or nohz) */
void tick_setup_oneshot(struct clock_event_device *newdev,
void (*handler)(struct clock_event_device *),
ktime_t next_event)
{
newdev->event_handler = handler;
clockevents_set_mode(newdev, CLOCK_EVT_MODE_ONESHOT);
clockevents_program_event(newdev, next_event, true);
}
oneshot 모드로 클럭이벤트 디바이스를 설정하고 핸들러를 대입한 후 이벤트를 프로그램한다.
tick_is_broadcast_device()
kernel/time/tick-broadcast.c
/* * Check, if the device is the broadcast device */
int tick_is_broadcast_device(struct clock_event_device *dev)
{
return (dev && tick_broadcast_device.evtdev == dev);
}
요청한 클럭 이벤트 디바이스가 브로드캐스트 디바이스인지 여부를 반환한다.
틱 디바이스 이벤트 핸들러
틱 디바이스 핸들러 -1- (hz based, 저해상도 타이머)
tick_handle_periodic()
kernel/time/tick-common.c
/* * Event handler for periodic ticks */
void tick_handle_periodic(struct clock_event_device *dev)
{
int cpu = smp_processor_id();
ktime_t next = dev->next_event;
tick_periodic(cpu);
#if defined(CONFIG_HIGH_RES_TIMERS) || defined(CONFIG_NO_HZ_COMMON)
/*
* The cpu might have transitioned to HIGHRES or NOHZ mode via
* update_process_times() -> run_local_timers() ->
* hrtimer_run_queues().
*/
if (dev->event_handler != tick_handle_periodic)
return;
#endif
if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
return;
for (;;) {
/*
* Setup the next period for devices, which do not have
* periodic mode:
*/
next = ktime_add(next, tick_period);
if (!clockevents_program_event(dev, next, false))
return;
/*
* Have to be careful here. If we're in oneshot mode,
* before we call tick_periodic() in a loop, we need
* to be sure we're using a real hardware clocksource.
* Otherwise we could get trapped in an infinite
* loop, as the tick_periodic() increments jiffies,
* which then will increment time, possibly causing
* the loop to trigger again and again.
*/
if (timekeeping_valid_for_hres())
tick_periodic(cpu);
}
}
틱 디바이스가 periodic 모드로 동작하여 인터럽트가 발생하면 이 함수가 호출되며 이 때 tick_periodic() 함수를 호출한다. oneshot 모드를 사용하는 클럭 이벤트 디바이스인 경우 다음 tick을 프로그램한다.
- 코드 라인 6에서 정규 틱 마다 할 일(스케줄, account process, rcu, irq work, posix cpu 타이머 등)을 처리한다. 추가로 현재 cpu가 do_timer() 호출 전담인 경우 jiffies를 1 증가시키고 wall time 등을 갱신한다.
- 코드 라인 13~14에서 periodic 핸들러가 아닌 경우 함수를 빠져나간다.
- 코드 라인 16~17에서 클럭 이벤트 디바이스가 이미 oneshot 모드가 아닌 경우 더 이상 처리를 할 필요 없으므로 핸들러를 빠져나간다.
- 결국 periodic 모드에서는 tick 마다 인터럽트 발생하고 jiffies++하고 wall time 만 갱신한다.
- 코드 라인 18~38에서 tick에 대한 시간을 추가하여 oneshot 이벤트를 프로그램 한다. 프로그램에 실패한 경우 timekeeping용 클럭 소스에서 CLOCK_SOURCE_VALID_FOR_HRES 플래그를 사용했으면 다시 한 번 jiffies를 1 증가시키고 wall time 등을 갱신하게 한다. 그 후 다시 반복한다.
tick_periodic()
kernel/time/tick-common.c
/* * Periodic tick */
static void tick_periodic(int cpu)
{
if (tick_do_timer_cpu == cpu) {
write_seqlock(&jiffies_lock);
/* Keep track of the next tick event */
tick_next_period = ktime_add(tick_next_period, tick_period);
do_timer(1);
write_sequnlock(&jiffies_lock);
update_wall_time();
}
update_process_times(user_mode(get_irq_regs()));
profile_tick(CPU_PROFILING);
}
정규 틱 마다 할 일(스케줄, account process, rcu, irq work, posix cpu 타이머 등)을 처리한다. 추가로 현재 cpu가 do_timer() 호출 전담인 경우jiffies를 1 증가시키고 글로벌 로드를 계산하고 wall time 등을 갱신한다
- 코드 라인 3~10에서 현재 cpu가 do_timer() 호출 전담인 경우 다음 틱 시간을 갱신하고 jiffies를 1 증가시키고 global load를 계산한다.
- 32bit arm은 jiffies 증가 시 64비트인 jiffies_64를 사용하므로 접근 시에는 반드시 시퀀스 락을 사용하여야 한다.
- 코드 라인 11에서 wall time을 갱신한다.
- 코드 라인 14에서 cpu 처리 시간을 갱신한다.
- 코드 라인 15에서 cpu profile 정보를 남긴다
do_timer()
kernel/time/timekeeping.c
/* * Must hold jiffies_lock */
void do_timer(unsigned long ticks)
{
jiffies_64 += ticks;
calc_global_load(ticks);
}
jiffies 값을 요청한 ticks 만큼 증가시키고 global load를 계산한다.
- global load 산출
- 계산 간격이 10 tick을 초과한 경우 1min, 5min, 15min 간격의 로드 평균 값을 avenrun[]에 갱신한다.
- 참고:
- Scheduler -2- (cpu load & PELT) | 문c
- Linux kernel load accounting | HAPPY HACKING
profile_tick()
kernel/profile.c
void profile_tick(int type)
{
struct pt_regs *regs = get_irq_regs();
if (!user_mode(regs) && prof_cpu_mask != NULL &&
cpumask_test_cpu(smp_processor_id(), prof_cpu_mask))
profile_hit(type, (void *)profile_pc(regs));
}
cpu profile 정보를 남긴다.
- 코드 라인 3에서 마지막 exception 프레임의 주소가 담긴 현재 cpu의 프레임 포인터를 알아온다.
- 코드 라인 5~7에서 커널에서 exception되었으며 prof_cpu_mask에 현재 cpu가 포함된 경우 cpu profile 정보를 남긴다.
profile_hit()
include/linux/profile.h
/* * Single profiler hit: */
static inline void profile_hit(int type, void *ip)
{
/*
* Speedup for the common (no profiling enabled) case:
*/
if (unlikely(prof_on == type))
profile_hits(type, ip, 1);
}
커널이 요청 타입에 대한 profile 타입을 준비한 경우 ip 주소에 대해 profile 정보를 남긴다.
- 요청 타입이 profiling 중인 타입이 아닌 거나 profile 버퍼가 설정되지 않은 경우 처리를 포기한다.
- cpu, sched, sleep, kvm profiling 중 하나를 선택할 수 있다.
- “profile=[schedule,]<number>”
- 스케쥴 포인트에 대해 profile
- <number>
- step/bucket 사이즈를 2의 차수 값을 사용하며 통계 시간 기반 profile
- “profile=sleep”
- D-state sleeping (ms)에 대한 profile
- “profile=kvm”
- VM 종료에 대한 profile
- 참고:
- Profiling the kernel
- kerneltop | LWN.net
update_process_times()
kernel/time/timer.c
/* * Called from the timer interrupt handler to charge one tick to the current * process. user_tick is 1 if the tick is user time, 0 for system. */
void update_process_times(int user_tick)
{
struct task_struct *p = current;
/* Note: this timer irq context must be accounted for as well. */
account_process_tick(p, user_tick);
run_local_timers();
rcu_sched_clock_irq(user_tick);
#ifdef CONFIG_IRQ_WORK
if (in_irq())
irq_work_tick();
#endif
scheduler_tick();
if (IS_ENABLED(CONFIG_POSIX_TIMERS))
run_posix_cpu_timers();
}
매 틱마다 호출되는 처리해야 하는 루틴들을 묶어놓은 함수이다.
- 코드 라인 6에서 cpu가 처리되는 시간 비율을 측정한다. user, system 및 idle 타임으로 구분하여 측정한다
- 코드 라인 7에서 만료된 hrtimer 및 timer 함수를 호출한다.
- hrtimer는 periodic 모드에서만 호출된다. oneshot 모드는 별도의 hrtimer_interrupt() 핸들러에서 직접 처리한다.
- timer는 softirq에서 처리하도록 raise 한다.
- 코드 라인 8에서 rcu core에 대한 처리를 수행한다.
- 코드 라인 10~11에서 enque되어 대기중인 irq_work를 처리한다.
- 코드 라인 13에서 현재 동작중인 태스크의 스캐줄러에 스케줄 틱을 호출한다.
- 코드 라인 14~15에서 posix cpu 타이머에 대한 처리를 수행한다.
run_local_timers()
kernel/time/timer.c
/* * Called by the local, per-CPU timer interrupt on SMP. */
void run_local_timers(void)
{
struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
hrtimer_run_queues();
/* Raise the softirq only if required. */
if (time_before(jiffies, base->clk)) {
if (!IS_ENABLED(CONFIG_NO_HZ_COMMON))
return;
/* CPU is awake, so check the deferrable base. */
base++;
if (time_before(jiffies, base->clk))
return;
}
raise_softirq(TIMER_SOFTIRQ);
}
만료된 hrtimer 및 timer를 처리한다.
- 코드 라인 5에서 periodic 모드에서 호출된 경우 hrtimer 해시 리스트에서 대기중인 만료된 hrtimer 함수들을 처리하도록 호출한다.
- 코드 라인 7~14에서 jiffies 까지 모든 timer가 처리된 경우 함수를 빠져나간다.
- 코드 라인 15에서 타이머 휠을 처리하도록 softirq를 raise 한다.
틱 디바이스 핸들러 -2- (nohz 기반, 저해상도 타이머)
tick_nohz_handler()
kernel/time/tick-sched.c
/* * The nohz low res interrupt handler */
static void tick_nohz_handler(struct clock_event_device *dev)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
struct pt_regs *regs = get_irq_regs();
ktime_t now = ktime_get();
dev->next_event.tv64 = KTIME_MAX;
tick_sched_do_timer(now);
tick_sched_handle(ts, regs);
/* No need to reprogram if we are running tickless */
if (unlikely(ts->tick_stopped))
return;
while (tick_nohz_reprogram(ts, now)) {
now = ktime_get();
tick_do_update_jiffies64(now);
}
}
nohz 기반의 저해상도 타이머를 사용한 틱 인터럽트 핸들러 루틴이다.
- 코드 라인 9에서 jiffies를 update하고 wall time을 조정한다.
- 코드 라인 10에서 process accounting 및 profile에 관련한 일들을 처리한다.
- 코드 라인 13~14에서 낮은 확률로 tick이 정지된 경우 함수를 빠져나간다.
- 코드 라인 16~19에서 tick을 재프로그램한다. 실패 시 반복한다.
틱 디바이스 핸들러 -3- (hz/nohz 기반, 고해상도 타이머)
hrtimer를 사용하여 tick을 프로그래밍한 인터럽트로 인해 hrtimer_interrupt()가 호출되면 등록된 tick 스케쥴 핸들러에 도달한다.
tick_sched_timer()
kernel/time/tick-sched.c
/* * We rearm the timer until we get disabled by the idle code. * Called with interrupts disabled. */
static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer)
{
struct tick_sched *ts =
container_of(timer, struct tick_sched, sched_timer);
struct pt_regs *regs = get_irq_regs();
ktime_t now = ktime_get();
tick_sched_do_timer(now);
/*
* Do not call, when we are not in irq context and have
* no valid regs pointer
*/
if (regs)
tick_sched_handle(ts, regs);
else
ts->next_tick = 0;
/* No need to reprogram if we are in idle or full dynticks mode */
if (unlikely(ts->tick_stopped))
return HRTIMER_NORESTART;
hrtimer_forward(timer, now, tick_period);
return HRTIMER_RESTART;
}
hz/nohz 기반의 고해상도 타이머를 사용한 틱 인터럽트 핸들러 루틴이다.
- 코드 라인 8에서 jiffies를 update하고 wall time을 조정한다.
- 코드 라인 14~17에서 process accounting 및 profile에 관련한 일들을 처리한다.
- 코드 라인 20~21에서 틱 스케쥴러에서 틱을 멈춰달라는 요청이 있는 경우 HRTIMER_NORESTART를 결과로 함수를 빠져나간다.
- 코드 라인 23~25에서 틱을 다시 프로그램하고 HRTIMER_RESTART를 결과로 함수를 빠져나간다.
Tick 디바이스의 Oneshot 모드 전환
periodic 틱에서 매번 oneshot 준비 상태 확인
틱 디바이스가 periodic 모드로 동작할 때 매 tick 인터럽트마다 호출되어 처리되는 함수 중 update_process_times() -> run_local_timers() -> hrtimer_run_queues() 함수 내에서 periodic 모드용 hrtimer(for hardirq)를 처리한다. hrtimer_run_queues() 함수 내부에서 high-resolution hw 타이머가 준비되어 틱 모드를 oneshot으로 전환할 수 있는지 여부를 체크하는 tick_check_oneshot_change() 함수를 통해 oneshot으로 전환된다. 이렇게 oneshot 모드 전환되면 hrtimer 처리 경로가 바뀌게 된다.
hrtimer_run_queues()
kernel/time/hrtimer.c
/* * Called from run_local_timers in hardirq context every jiffy */
void hrtimer_run_queues(void)
{
struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
unsigned long flags;
ktime_t now;
if (__hrtimer_hres_active(cpu_base))
return;
/*
* This _is_ ugly: We have to check periodically, whether we
* can switch to highres and / or nohz mode. The clocksource
* switch happens with xtime_lock held. Notification from
* there only sets the check bit in the tick_oneshot code,
* otherwise we might deadlock vs. xtime_lock.
*/
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) {
hrtimer_switch_to_hres();
return;
}
raw_spin_lock_irqsave(&cpu_base->lock, flags);
now = hrtimer_update_base(cpu_base);
if (!ktime_before(now, cpu_base->softirq_expires_next)) {
cpu_base->softirq_expires_next = KTIME_MAX;
cpu_base->softirq_activated = 1;
raise_softirq_irqoff(HRTIMER_SOFTIRQ);
}
__hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_HARD);
raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
}
hrtimer 해시 리스트에서 대기중인 만료된 hrtimer를 처리한다. 이 루틴은 periodic 틱에서만 동작한다.
- 코드 라인 7~8에서 oneshot 모드로 전환하여 이미 hres 타이머가 active된 경우 함수를 빠져나간다.
- 코드 라인 17~20에서 oneshot 모드로 전환한다. 만일 hres 타이머를 사용할 수 있는 경우 hres 타이머용 oneshot/nohz 모드로 전환한다.
- oneshot 모드 전환 시 두 가지 모드 지원
- low-resolution 타이머용 nohz 전환
- high-reslution 타이머용 nohz 전환
- oneshot 모드 전환 시 두 가지 모드 지원
- 코드 라인 23에서 periodic 모드인 경우에만 처리되는데 현재 cpu의 hrtimer 시각을 알아온다.
- 코드 라인 25~29에서 softirq용 hrtimer가 만료된 경우 이를 처리하도록 softirq를 raise 한다.
- 코드 라인 31에서 hardirq용 hrtimer에 대한 처리를 수행한다.
hrtimer_hres_active()
kernel/time/hrtimer.c
static inline int hrtimer_hres_active(void)
{
return __hrtimer_hres_active(this_cpu_ptr(&hrtimer_bases));
}
현재 cpu의 hrtimer가 고해상도 모드로 동작중인지 여부를 반환한다.
/* * Is the high resolution mode active ? */
static inline int __hrtimer_hres_active(struct hrtimer_cpu_base *cpu_base)
{
return IS_ENABLED(CONFIG_HIGH_RES_TIMERS) ?
cpu_base->hres_active : 0;
}
@cpu_base의 hrtimer가 고해상도 모드로 동작중인지 여부를 반환한다.
hrtimer_is_hres_enabled()
kernel/time/hrtimer.c
/* * hrtimer_high_res_enabled - query, if the highres mode is enabled */
static inline int hrtimer_is_hres_enabled(void)
{
return hrtimer_hres_enabled;
}
고해상도모드의 hrtimer가 enable되어 있는지 여부를 반환한다.
- 디폴트 값으로 on되어 있고 “highres=off” 커널 파라메터를 사용하여 disable할 수 있다.
tick_check_oneshot_change()
kernel/time/tick-sched.c
/** * Check, if a change happened, which makes oneshot possible. * * Called cyclic from the hrtimer softirq (driven by the timer * softirq) allow_nohz signals, that we can switch into low-res nohz * mode, because high resolution timers are disabled (either compile * or runtime). Called with interrupts disabled. */
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)
return 0;
if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
return 0;
if (!allow_nohz)
return 1;
tick_nohz_switch_to_nohz();
return 0;
}
oneshot 모드로의 변경이 일어난 경우 체크한다.
- 코드 라인 5~6에서 tick_sched의 check_clocks에서 0번 cpu에 해당하는 비트가 설정된 경우 클리어한다. 설정되지 않은 경우 0을 반환한다.
- 코드 라인 8~9에서 nohz 모드가 활성화되지 않은 경우 0을 반환한다.
- 코드 라인 11~12에서 timekeeping에 사용하는 클럭 이벤트 소스의 플래그에 valid_for_hres가 설정되지 않았거나 틱 이벤트 디바이스가 oneshot 모드가 아닌 경우 0을 반환한다.
- 코드 라인 14~15에서 인수 allow_nohz가 0인 경우 1을 반환한다.
- 코드 라인 17~18에서 low-resolution 타이머용 nohz 모드로 전환하고 hrtimer를 사용하여 다음 스케쥴 틱을 프로그래밍 한다.
oneshot 모드 및 low-resolution nohz 전환
tick_nohz_switch_to_nohz()
kernel/time/tick-sched.c
/** * tick_nohz_switch_to_nohz - switch to nohz mode */
static void tick_nohz_switch_to_nohz(void)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
ktime_t next;
if (!tick_nohz_enabled)
return;
if (tick_switch_to_oneshot(tick_nohz_handler))
return;
/*
* Recycle the hrtimer in ts, so we can share the
* hrtimer_forward with the highres code.
*/
hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_HARD);
/* Get the next period */
next = tick_init_jiffy_update();
hrtimer_set_expires(&ts->sched_timer, next);
hrtimer_forward_now(&ts->sched_timer, tick_period);
tick_program_event(hrtimer_get_expires(&ts->sched_timer), 1);
tick_nohz_activate(ts, NOHZ_MODE_LOWRES);
}
oneshot 및 nohz 모드로 전환하고 hrtimer를 사용하여 다음 스케쥴 틱을 프로그래밍 한다.
- 코드 라인 6~7에서 nohz가 enable되어 있지 않은 경우 함수를 빠져나간다.
- 코드 라인 9~10에서 틱 디바이스를 oneshot 모드로 전환시키고 처리할 핸들러를 준비한다. 만일 전환이 실패하는 경우 함수를 빠져나간다.
- 코드 라인 16~22에서 hrtimer를 사용하여 1 jiffies 틱을 프로그램한다.
- 코드 라인 23에서 nohz 모드가 저해상도 타이머 모드로 동작됨을 틱 sched에 표시한다.
tick_switch_to_oneshot()
kernel/time/tick-oneshot.c
/** * tick_switch_to_oneshot - switch to oneshot mode */
int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *))
{
struct tick_device *td = this_cpu_ptr(&tick_cpu_device);
struct clock_event_device *dev = td->evtdev;
if (!dev || !(dev->features & CLOCK_EVT_FEAT_ONESHOT) ||
!tick_device_is_functional(dev)) {
printk(KERN_INFO "Clockevents: "
"could not switch to one-shot mode:");
if (!dev) {
printk(" no tick device\n");
} else {
if (!tick_device_is_functional(dev))
printk(" %s is not functional.\n", dev->name);
else
printk(" %s does not support one-shot mode.\n",
dev->name);
}
return -EINVAL;
}
td->mode = TICKDEV_MODE_ONESHOT;
dev->event_handler = handler;
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
tick_broadcast_switch_to_oneshot();
return 0;
}
틱 디바이스를 oneshot 모드로 전환시키고 처리할 핸들러를 준비한다.
- 이 함수는 다음 두 군데에서 호출되어 사용된다.
- 고해상도 타이머가 필요할 때 hrtimer_switch_to_hres() 함수 내부의 tick_init_hres() 함수에서 사용된다.
- dynamic tick을 처리하기 위해 nohz 모드로 전환하기 위해 tick_nohz_switch_to_nohz() 함수에서 사용된다.
- 코드 라인 6~21에서 clock_event_device가 준비되지 않았거나 oneshot 모드로 동작하지 않는 경우 에러 메시지를 출력하고 -EINVAL 에러를 반환한다.
- 코드 라인 23~25에서 인수로 받은 이벤트 핸들러를 디바이스에 대입한다. 또한 clock_event_device의 set_mode() 후크를 통해 드라이버에서 oneshot 모드를 설정하게 한다.
- rpi2: arch_timer_set_mode_virt() 호출되는데 shutdown 모드가 아닌 oneshot 모드에서는 특별히 설정하는 것이 없다.
- 코드 라인 26에서 tick broadcast 디바이스를 위해 oneshot 모드로 변경한다.
tick_broadcast_switch_to_oneshot()
kernel/time/tick-broadcast.c
/* * Select oneshot operating mode for the broadcast device */
void tick_broadcast_switch_to_oneshot(void)
{
struct clock_event_device *bc;
unsigned long flags;
raw_spin_lock_irqsave(&tick_broadcast_lock, flags);
tick_broadcast_device.mode = TICKDEV_MODE_ONESHOT;
bc = tick_broadcast_device.evtdev;
if (bc)
tick_broadcast_setup_oneshot(bc);
raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
}
tick broadcast 디바이스의 모드를 oneshot 모드로 바꾸고 tick broadcast 디바이스의 클럭 이벤트 디바이스가 준비된 경우 다음 tick을 설정한다.
oneshot 모드 및 high-resolution nohz 전환
hrtimer_switch_to_hres()
kernel/time/hrtimer.c
/* * Switch to high resolution mode */
static void hrtimer_switch_to_hres(void)
{
struct hrtimer_cpu_base *base = this_cpu_ptr(&hrtimer_bases);
if (tick_init_highres()) {
pr_warn("Could not switch to high resolution mode on CPU %u\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);
}
hrtimer를 high resolution 모드로 설정하고 스케쥴 틱 타이머를 동작시킨다.
- 코드 라인 5~9에서 틱 디바이스를 oneshot 모드로 전환한다. 전환되지 않는 경우 경고 메시지를 출력하고 함수를 빠져나간다.
- 코드 라인 10~11에서 hrtimer가 high resolution 상태임을 설정하고 4 개 클럭의 해상도에 1ns를 대입한다.
- 코드 라인 13에서 스케줄 틱을 설정한다.
- 코드 라인 15에서 hrtimer를 리프로그램하고 1을 반환한다.
tick_init_highres()
kernel/time/tick-oneshot.c
/** * tick_init_highres - switch to high resolution mode * * Called with interrupts disabled. */
int tick_init_highres(void)
{
return tick_switch_to_oneshot(hrtimer_interrupt);
}
고해상도 모드의 oneshot 타이머를 동작시키고 인터럽트 발생 시 hrtimer 인터럽트 핸들러 콜백 함수인 hrtimer_interrupt() 함수를 호출하게 한다.
고해상도 hrtimer를 사용한 Sched Tick 설정
tick_setup_sched_timer()
kernel/time/tick-sched.c
/** * tick_setup_sched_timer - setup the tick emulation timer */
void tick_setup_sched_timer(void)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
ktime_t now = ktime_get();
/*
* Emulate tick processing via per-CPU hrtimers:
*/
hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_HARD);
ts->sched_timer.function = tick_sched_timer;
/* Get the next period (per-CPU) */
hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());
/* Offset the tick to avert jiffies_lock contention. */
if (sched_skew_tick) {
u64 offset = ktime_to_ns(tick_period) >> 1;
do_div(offset, num_possible_cpus());
offset *= smp_processor_id();
hrtimer_add_expires_ns(&ts->sched_timer, offset);
}
hrtimer_forward(&ts->sched_timer, now, tick_period);
hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS_PINNED_HARD);
tick_nohz_activate(ts, NOHZ_MODE_HIGHRES);
}
고해상도 hrtimer를 사용하여 틱 스케쥴 타이머를 가동한다. 또한 nohz 모드로 설정한다.
- 코드 라인 9~10에서 틱 스케쥴에서 사용할 hrtimer를 초기화하고 핸들러 함수를 지정한다.
- tick_sched_timer()에서 jiffies 관리 cpu를 지정하고, 매번 틱을 프로그램한다.
- 코드 라인 13에서 틱 스케쥴용 hrtimer의 만료 시간은 1 jiffies 기간으로 한다.
- 코드 라인 16~21에서 jiffies lock 혼잡을 피하기 위해 cpu 마다 offset을 산출하고 추가하여 틱 만료시간을 재설정한다.
- 코드 라인 23~24에서 hrtimer를 forward한 후 프로그램한다.
- 코드 라인 25에서 nohz 모드를 highres로 변경하고 active 되었음을 설정한다.
oneshot 틱 프로그램
tick_program_event()
kernel/time/tick-oneshot.c
/** * tick_program_event */
int tick_program_event(ktime_t expires, int force)
{
struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);
return clockevents_program_event(dev, expires, force);
}
요청한 만료 시간에 틱이 발생하도록 해당 cpu의 클럭 이벤트 디바이스에 프로그램한다.
틱 브로드캐스트 디바이스 핸들러
tick_handle_periodic_broadcast()
kernel/time/tick-broadcast.c
/* * Event handler for periodic broadcast ticks */
static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
{
struct tick_device *td = this_cpu_ptr(&tick_cpu_device);
bool bc_local;
raw_spin_lock(&tick_broadcast_lock);
/* Handle spurious interrupts gracefully */
if (clockevent_state_shutdown(tick_broadcast_device.evtdev)) {
raw_spin_unlock(&tick_broadcast_lock);
return;
}
bc_local = tick_do_periodic_broadcast();
if (clockevent_state_oneshot(dev)) {
ktime_t next = ktime_add(dev->next_event, tick_period);
clockevents_program_event(dev, next, true);
}
raw_spin_unlock(&tick_broadcast_lock);
/*
* We run the handler of the local cpu after dropping
* tick_broadcast_lock because the handler might deadlock when
* trying to switch to oneshot mode.
*/
if (bc_local)
td->evtdev->event_handler(td->evtdev);
}
브로드캐스트가 필요한 cpu를 대상으로 브로드캐스트하여 cpu를 idle 상태에서 깨운다. 클럭 이벤트 디바이스의 모드가 oneshot인 경우 틱을 프로그램한다.
- 코드 라인 9~12에서 브로드 캐스트에 사용할 클럭 이벤트 디바이스가 shutdown된 상태라면 함수를 빠져나간다.
- 코드 라인 14에서 브로드캐스트가 필요한 cpu를 대상으로 브로드캐스트하여 cpu를 idle 상태에서 깨운다.
- 코드 라인 16~20에서 클럭 이벤트 디바이스가 oneshot 모드인 경우 틱을 프로그래밍한다.
- 코드 라인 28~29에서 브로드 캐스트용 이벤트 핸들러를 호출한다.
다음 그림은 nohz idle 상태에 있는 cpu#1~#3 들을 깨우도록 브로드캐스트 하는 과정을 보여준다.
- 틱 브로드캐스트 디바이스가 periodic 모드로 동작
tick_handle_oneshot_broadcast()
kernel/time/tick-broadcast.c
/* * Handle oneshot mode broadcasting */
static void tick_handle_oneshot_broadcast(struct clock_event_device *dev)
{
struct tick_device *td;
ktime_t now, next_event;
int cpu, next_cpu = 0;
bool bc_local;
raw_spin_lock(&tick_broadcast_lock);
dev->next_event = KTIME_MAX;
next_event = KTIME_MAX;
cpumask_clear(tmpmask);
now = ktime_get();
/* Find all expired events */
for_each_cpu(cpu, tick_broadcast_oneshot_mask) {
/*
* Required for !SMP because for_each_cpu() reports
* unconditionally CPU0 as set on UP kernels.
*/
if (!IS_ENABLED(CONFIG_SMP) &&
cpumask_empty(tick_broadcast_oneshot_mask))
break;
td = &per_cpu(tick_cpu_device, cpu);
if (td->evtdev->next_event <= now) {
cpumask_set_cpu(cpu, tmpmask);
/*
* Mark the remote cpu in the pending mask, so
* it can avoid reprogramming the cpu local
* timer in tick_broadcast_oneshot_control().
*/
cpumask_set_cpu(cpu, tick_broadcast_pending_mask);
} else if (td->evtdev->next_event < next_event) {
next_event = td->evtdev->next_event;
next_cpu = cpu;
}
}
/*
* Remove the current cpu from the pending mask. The event is
* delivered immediately in tick_do_broadcast() !
*/
cpumask_clear_cpu(smp_processor_id(), tick_broadcast_pending_mask);
/* Take care of enforced broadcast requests */
cpumask_or(tmpmask, tmpmask, tick_broadcast_force_mask);
cpumask_clear(tick_broadcast_force_mask);
/*
* Sanity check. Catch the case where we try to broadcast to
* offline cpus.
*/
if (WARN_ON_ONCE(!cpumask_subset(tmpmask, cpu_online_mask)))
cpumask_and(tmpmask, tmpmask, cpu_online_mask);
/*
* Wakeup the cpus which have an expired event.
*/
bc_local = tick_do_broadcast(tmpmask);
/*
* Two reasons for reprogram:
*
* - The global event did not expire any CPU local
* events. This happens in dyntick mode, as the maximum PIT
* delta is quite small.
*
* - There are pending events on sleeping CPUs which were not
* in the event mask
*/
if (next_event != KTIME_MAX)
tick_broadcast_set_event(dev, next_cpu, next_event);
raw_spin_unlock(&tick_broadcast_lock);
if (bc_local) {
td = this_cpu_ptr(&tick_cpu_device);
td->evtdev->event_handler(td->evtdev);
}
}
브로드캐스트가 필요한 cpu를 대상으로 브로드캐스트하여 cpu를 idle 상태에서 깨운다. 클럭 이벤트 디바이스의 모드가 oneshot인 경우 틱을 프로그램한다.
- 코드 라인 14~36에서 브로드 캐스트가 필요한 cpu들에 대해 틱 만료된 cpu들을 tmp 마스크에 알아온다.
- 코드 라인 42~46에서 현재 cpu는 pending 마스크에서 제거하고 tmp 마스크 대상에 force 마스크를 추가한다.
- 코드 라인 52~53에서 tmp 마스크에서 online된 cpu들은 제거한다.
- 코드 라인 58에서 tmp 마스크를 대상으로 브로드캐스트를 수행하여 idle 상태에 있는 cpu들을 모두 깨운다.
- 코드 라인 70~71에서 남아있는 이벤트가 있는 경우 브로드캐스트 디바이스로 사용되는 클럭 이벤트 디바이스의 모드를 oneshot으로 변경하고 프로그램한다.
- 코드 라인 75~78에서 등록된 브로드 캐스트용 이벤트 핸들러를 호출한다.
다음 그림은 nohz idle 상태에 있는 cpu#1~#3 들을 깨우도록 브로드캐스트 하는 과정을 보여준다.
- 틱 브로드캐스트 디바이스가 oneshot 모드로 동작
다음 그림은 nohz를 사용하여 idle 상태에 있는 3개의 cpu가 브로드캐스트에 의해 각 만료시간마다 깨어나는 과정을 보여준다.
- 별표와 같이 프로그램된 틱들이 브로드캐스트를 발송하는 cpu의 틱 타임에 맞춰 약간씩 지연되는 모습을 볼 수 있다.
tick_broadcast_set_event()
kernel/time/tick-broadcast.c
static void tick_broadcast_set_event(struct clock_event_device *bc, int cpu,
ktime_t expires)
{
if (!clockevent_state_oneshot(bc))
clockevents_switch_state(bc, CLOCK_EVT_STATE_ONESHOT);
clockevents_program_event(bc, expires, 1);
tick_broadcast_set_affinity(bc, cpumask_of(cpu));
}
틱 브로드캐스트 디바이스에서 사용되는 클럭 이벤트 디바이스의 모드를 oneshot으로 변경하고 프로그램 한다. 만일 실패하는 경우 요청 cpu로 irq affinity를 설정한다.
tick_broadcast_set_affinity()
kernel/time/tick-broadcast.c
/* * Set broadcast interrupt affinity */
static void tick_broadcast_set_affinity(struct clock_event_device *bc,
const struct cpumask *cpumask)
{
if (!(bc->features & CLOCK_EVT_FEAT_DYNIRQ))
return;
if (cpumask_equal(bc->cpumask, cpumask))
return;
bc->cpumask = cpumask;
irq_set_affinity(bc->irq, bc->cpumask);
}
클럭 이벤트 디바이스의 인터럽트를 요청한 cpu 들이 수신할 수 있도록 설정한다. 단 FEAT_DYNIRQ 기능이 없는 클럭 이벤트 디바이스는 수행할 수 없다.
C3Stop과 틱 브로드캐스트
타이머의 절전 기능이 구현된 SoC 즉, c3stop이 설정되었거나 per-cpu 타이머가 없는 경우 cpu가 deep idle 상태에 진입하고 나갈 때 마다 전역 비트맵에 알림 요청을 하고 다른 cpu에서 deep idle 상태의 cpu들을 broadcast 기능을 통해 깨울 수 있다.
cpuidle_setup_broadcast_timer()
drivers/cpuidle/driver.c
/** * cpuidle_setup_broadcast_timer - enable/disable the broadcast timer on a cpu * @arg: a void pointer used to match the SMP cross call API * * If @arg is NULL broadcast is disabled otherwise enabled * * This function is executed per CPU by an SMP cross call. It's not * supposed to be called directly. */
static void cpuidle_setup_broadcast_timer(void *arg)
{
if (arg)
tick_broadcast_enable();
else
tick_broadcast_disable();
}
틱 브로드캐스트 기능을 켜거나 끈다.
tick_broadcast_enable()
include/linux/tick.h
static inline void tick_broadcast_enable(void)
{
tick_broadcast_control(TICK_BROADCAST_ON);
}
틱 브로드캐스트 기능을 켠다.
tick_broadcast_disable()
include/linux/tick.h
static inline void tick_broadcast_disable(void)
{
tick_broadcast_control(TICK_BROADCAST_OFF);
}
틱 브로드캐스트 기능을 끈다.
tick_broadcast_control()
kernel/time/tick-broadcast.c
/** * tick_broadcast_control - Enable/disable or force broadcast mode * @mode: The selected broadcast mode * * Called when the system enters a state where affected tick devices * might stop. Note: TICK_BROADCAST_FORCE cannot be undone. */
void tick_broadcast_control(enum tick_broadcast_mode mode)
{
struct clock_event_device *bc, *dev;
struct tick_device *td;
int cpu, bc_stopped;
unsigned long flags;
/* Protects also the local clockevent device. */
raw_spin_lock_irqsave(&tick_broadcast_lock, flags);
td = this_cpu_ptr(&tick_cpu_device);
dev = td->evtdev;
/*
* Is the device not affected by the powerstate ?
*/
if (!dev || !(dev->features & CLOCK_EVT_FEAT_C3STOP))
goto out;
if (!tick_device_is_functional(dev))
goto out;
cpu = smp_processor_id();
bc = tick_broadcast_device.evtdev;
bc_stopped = cpumask_empty(tick_broadcast_mask);
switch (mode) {
case TICK_BROADCAST_FORCE:
tick_broadcast_forced = 1;
/* fall through */
case TICK_BROADCAST_ON:
cpumask_set_cpu(cpu, tick_broadcast_on);
if (!cpumask_test_and_set_cpu(cpu, tick_broadcast_mask)) {
/*
* Only shutdown the cpu local device, if:
*
* - the broadcast device exists
* - the broadcast device is not a hrtimer based one
* - the broadcast device is in periodic mode to
* avoid a hickup during switch to oneshot mode
*/
if (bc && !(bc->features & CLOCK_EVT_FEAT_HRTIMER) &&
tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)
clockevents_shutdown(dev);
}
break;
case TICK_BROADCAST_OFF:
if (tick_broadcast_forced)
break;
cpumask_clear_cpu(cpu, tick_broadcast_on);
if (cpumask_test_and_clear_cpu(cpu, tick_broadcast_mask)) {
if (tick_broadcast_device.mode ==
TICKDEV_MODE_PERIODIC)
tick_setup_periodic(dev, 0);
}
break;
}
if (bc) {
if (cpumask_empty(tick_broadcast_mask)) {
if (!bc_stopped)
clockevents_shutdown(bc);
} else if (bc_stopped) {
if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)
tick_broadcast_start_periodic(bc);
else
tick_broadcast_setup_oneshot(bc);
}
}
out:
raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
}
EXPORT_SYMBOL_GPL(tick_broadcast_control);
현재 cpu의 상태 변화에 따른 브로드캐스트 여부를 결정할 수 있는 비트마스크 설정을 한다.
- 코드 라인 16~17에서 c3stop 기능이 없는 경우 함수를 빠져나간다.
- 코드 라인 19~20에서 기능이 구현되지 않은 dummy 디바이스인 경우 함수를 빠져나간다.
- 코드 라인 30~45에서 broadcast on 요청을 받은 경우 tick_broadcast_on 및 tick_broadcast_mask의 현재 cpu에 해당하는 비트를 설정한다. 만일 틱 브로드캐스트 모드가 periodic 이면서 tick이 발생중이면 tick을 정지시킨다.
- 코드 라인 47~56에서 broadcast off 요청을 받은 경우 tick_broadcast_on 및 tick_broadcast_mask의 현재 cpu에 해당하는 비트를 클리어한다. 만일 틱 브토드캐스트 모드가 periodic이면서 tick이 발생하지 않는 상태인 경우 tick을 발생시키도록 요청한다.
- 코드 라인 60~62에서 브로드캐스트할 cpu가 없는 경우 클럭이벤트 디바이스를 shutdown 한다.
- 코드 라인 63~68에서 브로드캐스트할 cpu가 있는 경우 틱 브로드캐스트 모드에 따라 periodic 또는 oneshot 모드로 설정한다.
다음 그림은 틱 브로드 캐스트를 켜고 끌 때 비트마스크들의 상황을 보여준다.
스케줄 틱 분석
먼저 타이머 인터럽트의 처리 경로를 다시 한 번 살펴보면 여러 개의 경로 중 rpi2의 경우 다음 타이머 인터럽트의 처리 경로가 있다.
- tick_handle_periodic()
- 루틴내에서 bottom-half 처리를 목적으로 softirq를 사용하여 타이머휠에 대한 처리를 수행한다. (run_timer_softirq())
- hrtimer_interrupt() → __run_hrtimer() → tick_sched_timer()
- hrtimer를 사용한 softirq는 커널 4.2.rc1부터 사용하지 않는다.
다음 그림은 스케줄틱이 처음 tick_handle_periodic() 함수로 처리되다가 hrtimer가 준비되면 tick_sched_timer() 함수로 이전되는 것을 보여준다.
nohz 진출입 함수
- tick_nohz_idle_enter()
- tick_nohz_idle_exit()
SMP 시스템에서 타이머 인터럽트가 발생되는 상황에 대해 다음 컴포넌트들의 상태와 비교하여 살펴보자
- cpu 마다 스케줄 틱은 조금씩 어긋나게 프로그램된다.
- 타이머 인터럽트를 발생시키는 틱 디바이스 상태 변화
- SMP 코어 중 하나는 jiffies를 규칙적으로 발생하므로 nohz로 진입할 수 없다. 그 외의 cpu들은 nohz 상태에 진입가능하다.
- 타이머를 프로그램하는 클럭이벤트디바이스 상태 변화
- armv7 & armv8에 내장된 아키텍처 타이머는 oneshot으로만 프로그래밍 가능하므로 매번 CONFIG_HZ 단위로 틱을 프로그래밍해야 한다.
구조체
tick_device 구조체
kernel/time/tick-sched.h
struct tick_device {
struct clock_event_device *evtdev;
enum tick_device_mode mode;
};
- *evtdev
- 틱 디바이스로 사용하는 클럭 이벤트 디바이스
- mode
- TICKDEV_MODE_PERIODIC
- nohz 모드를 사용하지 못하는 틱 디바이스 또는 부트업 과정
- TICKDEV_MODE_ONESHOT
- nohz 모드를 사용할 수 있는 틱 디바이스(possible)
- TICKDEV_MODE_PERIODIC
tick_sched 구조체
kernel/time/tick-sched.h
/** * struct tick_sched - sched tick emulation and no idle tick control/stats * @sched_timer: hrtimer to schedule the periodic tick in high * resolution mode * @check_clocks: Notification mechanism about clocksource changes * @nohz_mode: Mode - one state of tick_nohz_mode * @inidle: Indicator that the CPU is in the tick idle mode * @tick_stopped: Indicator that the idle tick has been stopped * @idle_active: Indicator that the CPU is actively in the tick idle mode; * it is resetted during irq handling phases. * @do_timer_lst: CPU was the last one doing do_timer before going idle * @got_idle_tick: Tick timer function has run with @inidle set * @last_tick: Store the last tick expiry time when the tick * timer is modified for nohz sleeps. This is necessary * to resume the tick timer operation in the timeline * when the CPU returns from nohz sleep. * @next_tick: Next tick to be fired when in dynticks mode. * @idle_jiffies: jiffies at the entry to idle for idle time accounting * @idle_calls: Total number of idle calls * @idle_sleeps: Number of idle calls, where the sched tick was stopped * @idle_entrytime: Time when the idle call was entered * @idle_waketime: Time when the idle was interrupted * @idle_exittime: Time when the idle state was left * @idle_sleeptime: Sum of the time slept in idle with sched tick stopped * @iowait_sleeptime: Sum of the time slept in idle with sched tick stopped, with IO outstanding * @timer_expires: Anticipated timer expiration time (in case sched tick is stopped) * @timer_expires_base: Base time clock monotonic for @timer_expires * @next_timer: Expiry time of next expiring timer for debugging purpose only * @tick_dep_mask: Tick dependency mask - is set, if someone needs the tick */
struct tick_sched {
struct hrtimer sched_timer;
unsigned long check_clocks;
enum tick_nohz_mode nohz_mode;
unsigned int inidle : 1;
unsigned int tick_stopped : 1;
unsigned int idle_active : 1;
unsigned int do_timer_last : 1;
unsigned int got_idle_tick : 1;
ktime_t last_tick;
ktime_t next_tick;
unsigned long idle_jiffies;
unsigned long idle_calls;
unsigned long idle_sleeps;
ktime_t idle_entrytime;
ktime_t idle_waketime;
ktime_t idle_exittime;
ktime_t idle_sleeptime;
ktime_t iowait_sleeptime;
unsigned long last_jiffies;
u64 timer_expires;
u64 timer_expires_base;
u64 next_timer;
ktime_t idle_expires;
atomic_t tick_dep_mask;
};
- sched_timer
- hres timer가 활성된 이후 스케줄 틱을 발생하는 hrtimer
- check_clocks
- 클럭 소스의 변화가 있거나 틱 디바이스 모드가 변경될 때 1로 설정된다.
- nohz_mode
- NOHZ_MODE_INACTIVE
- nohz 모드가 동작하지 않는 경우
- NOHZ_MODE_LOWRES
- low-resolution 타이머를 사용하는 nohz 모드
- NOHZ_MODE_HIGHRES
- high-resolution 타이머를 사용하는 nohz 모드
- NOHZ_MODE_INACTIVE
- inidle:1
- nohz idle 진입 여부
- tick_nohz_idle_enter() 함수에서 1로 설정하고, tick_nohz_idle_exit() 함수에서 0으로 클리어한다.
- tick_stopped:1
- nohz로 인해 tick이 멈춘 상태 여부 (nohz idle & nohz full)
- idle_active:1
- cpu가 틱 idle 모드인지 여부
- do_timer_last:1
- idle 진입 전 do_timer() 함수를 수행했었는지 여부
- last_tick
- 마지막 프로그래밍된 틱 시간(ns)
- nohz를 멈추고 다시 periodic 틱을 발생 시킬 때 기억해둔 이 틱 시간 이후로 틱을 forward하여 사용한다.
- idle_expires
- nohz idle 진입 시 nohz 만료 예정 시각(ns)
참고
- Timer -1- (Lowres Timer) | 문c
- Timer -2- (HRTimer) | 문c
- Timer -3- (Clock Sources Subsystem) | 문c
- Timer -4- (Clock Sources Watchdog) | 문c
- Timer -5- (Clock Events Subsystem) | 문c
- Timer -6- (Clock Source & Timer Driver) | 문c
- Timer -7- (Sched Clock & Delay Timers) | 문c
- Timer -8- (Timecounter) | 문c – 현재 글
- Timer -9- (Tick Device) | 문c
- Timer -10- (Timekeeping) | 문c
- Timer -11- (Posix Clock & Timers) | 문c
- time_init() | 문c
- sched_clock_postinit() | 문c
- tick_init() | 문c
- timekeeping_init() | 문c
- calibrate_delay() | 문c
- The tick broadcast framework | LWN.net













