- 时钟系统,频率的产生
- RTC时钟系统
- Linux时钟中断
- struct clocksource
- struct timekeeper
- struct clock_event_device
- struct tick_device
- 链接
1. cpu的时间产生不一定如此,用来阐述硬件产生滴答的原理 2. 在断电情况下 RTC仍可以独立运行 只要芯片的备用电源一直供电,RTC上的时间会一直走;可以产生频率; 3. 通过振荡器作为时钟源被用来驱动系统时钟;2种二级时钟源,40KHz低速内部RC,32.768KHz低速外部晶体;Linux时钟中断
1. Linux的OS时钟的物理产生原因是可编程定时/计数器产生的输出脉冲,这个脉冲送入CPU,就可以引发一个中断请求信号,我们就把它叫做时钟中断; 2. 操作系统对可编程定时/计数器进行有关初始化,然后定时/计数器就对输入脉冲进行计数(分频),产生的三个输出脉冲Out0、Out1、Out2各有用途,很多接口书都介绍了这个问题,我们只看Out0上的输出脉冲,这个脉冲信号接到中断控制器8259A_1的0号管脚,触发一个周期性的中断,我们就把这个中断叫做时钟中断,时钟中断的周期,也就是脉冲信号的周期,我们叫做“滴答”或“时标”(tick); 3.
struct clocksource {
u64 (*read)(struct clocksource *cs);
u64 mask;
u32 mult;
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);
void (*mark_unstable)(struct clocksource *cs);
void (*tick_stable)(struct clocksource *cs);
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
struct list_head wd_list;
u64 cs_last;
u64 wd_last;
#endif
struct module *owner;
};
1. 该结构体是对真实的时钟源的进行的软件抽象; 2. 该抽象的时钟源本身不会产生中断,要获得当前时钟源的计数,需要主动调用read回调函数获得当前的计数值,即cycle,然后借助成员变量mult和shift来完成计算; 3. 内核的启动阶段,会注册一个基于jiffies的clocksource ,然后注册到系统中,注意,注册时候,成员变量还没有赋值; 4. 时钟发生器是硬件设备,通过时钟发生器产生的循环,可以读到cycle信息,进而获得时间;(对时间产生的理解)struct timekeeper
struct timekeeper {
struct tk_read_base tkr_mono;
struct tk_read_base tkr_raw;
u64 xtime_sec;
unsigned long ktime_sec;
struct timespec64 wall_to_monotonic;
ktime_t offs_real;
ktime_t offs_boot;
ktime_t offs_tai;
s32 tai_offset;
unsigned int clock_was_set_seq;
u8 cs_was_changed_seq;
ktime_t next_leap_ktime;
u64 raw_sec;
u64 cycle_interval;
u64 xtime_interval;
s64 xtime_remainder;
u64 raw_interval;
u64 ntp_tick;
s64 ntp_error;
u32 ntp_error_shift;
u32 ntp_err_mult;
u32 skip_second_overflow;
#ifdef CONFIG_DEBUG_TIMEKEEPING
long last_warning;
int underflow_seen;
int overflow_seen;
#endif
};
1. 时间的分类 - RTC时间:通过专门的硬件实现;毫秒级别,如果位于外部的RTC芯片,访问速度也比较慢; - xtime:墙上时间,内存中的变量,精度较高,可以达到纳秒级别;CLOCK_REALTIME - monotonic time:自系统开机就一直增加,不计算系统的休眠的时间;CLOCK_MONOTONIC - raw monotonic time:代表独立时钟硬件对时间的统计;CLOCK_MONOTONIC_RAW 2. 上述时间是依赖于结构成员clock指向的时钟源的;更新clocksource会通过通知链通知timekeeper作时钟源的改变; 3. 默认的clocksource是基于jiffies的clocksource_jiffies;利用rtc的当前时间,对timekeeper中的成员变量xtime,raw_time,wall_to_monotonic等字段进行描述;然后初始化代表实际时间与monotonic时间之间的偏移量的offs_read字段,total_sleep_time字段初始化为0; 4. xtime字段因为是保存在内存中,系统掉电后无法保存时间信息,所以每次启动时都要通过timekeeping_init从RTC中同步正确的时间信息; 5. xtime同步完RTC的数据之后,timekeeper就会独立于rtc,利用自身关联的clocksource进行时间的更新;每次xtime更新会调用一次do_timer,增加jiffies计数; 6. 根据内核的配置项的不同,更新时间的操作发生的频度也不尽相同,如果没有配置NO_HZ选项,通常每个tick的定时中断周期,do_timer会被调用一次,相反,如果配置了NO_HZ选项,可能会在好几个tick后,do_timer才会被调用一次;
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;
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 *);
int (*set_state_oneshot)(struct clock_event_device *);
int (*set_state_oneshot_stopped)(struct clock_event_device *);
int (*set_state_shutdown)(struct clock_event_device *);
int (*tick_resume)(struct clock_event_device *);
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;
int irq;
int bound_on;
const struct cpumask *cpumask;
struct list_head list;
struct module *owner;
} ____cacheline_aligned;
struct sys_timer {
void (*init)(void);
void (*suspend)(void);
void (*resume)(void);
#ifdef CONFIG_ARCH_USES_GETTIMEOFFSET
unsigned long (*offset)(void);
#endif
}
1. clock_event_device与clocksource的差别:clocksource不能被编程,没有产生事件的能力,它主要被用于timekeeper来实现对真实时间进行精确的统计;而clock_event_device则是可编程的,它可以工作在周期触发或单次触发模式,系统可以对它进行编程,以确定下一次事件触发的时间,clock_event_device主要用于实现普通定时器和高精度定时器,同时也用于产生tick事件,供给进程调度子系统使用; 2. tick_device是基于clock_event_device的进一步封装,用于代替原有的时钟滴答中断,给内核提供tick事件,以完成进程的调度和进程信息统计,负载平衡和时间更新等操作; 3. 时钟事件设备的上层抽象如图,与硬件相关的代码进行了分离; 4. 时钟事件设备的核心数据结构是clock_event_device结构,它代表着一个时钟硬件设备,该设备就好像是一个具有事件触发能力(通常就是指中断)的clocksource,它不停地计数,当计数值达到预先编程设定的数值那一刻,会引发一个时钟事件中断,继而触发该设备的事件处理回调函数,以完成对时钟事件的处理; 5. 每一个machine,都要定义一个自己的machine_desc结构,该结构定义了该machine的一些最基本的特性,其中需要设定一个sys_timer结构指针,machine级的代码负责定义sys_timer结构;该init回调函数的主要作用就是完成系统中的clocksource和clock_event_device的硬件初始化工作;同时注册通知链;
struct tick_device {
struct clock_event_device *evtdev;
enum tick_device_mode mode;
};
1. 当内核没有配置成支持高精度定时器时,系统的tick由tick_device产生,tick_device其实是clock_event_device的简单封装,它内嵌了一个clock_event_device指针和它的工作模式; 2. DEFINE_PER_CPU(struct tick_device, tick_cpu_device); 3. 在更新clock_event_deviec时,通知链会调用到tick_check_new_device函数判断是否可以用于本cpu; 4. TICKDEV_MODE_PERIODIC或TICKDEV_MODE_ONESHOT模式;oneshot模式才会支持NO_HZ和HRTIMER,其根据tick_device的特性设置; 5. tick事件的处理:tick_checkouk_new_device之后; 6. 在do_timer函数中,完成一下操作;更新jiffies的时间,更新墙上时间,每10个tick,更新一次cpu的负载信息;通过调用update_process_times完成以下工作:1).更新进程的时间统计信息 2).触发TIMER_SOFTIRQ软件中断,以便系统处理传统的低分辨率定时器;3).检查rcu的callback;4). 通过scheduler_tick触发调度系统进行进程统计和调度工作;链接
RTC实时时钟,步骤超细详解,一文看懂RTC
Linux时间子系统之一:clock source(时钟源)
Linux之时钟中断



