jiffies与jiffies_64
64位系统:两个是同一个变量(unsigned long long)
32位系统:jiffies为unsigned long是jiffies_64的低32位
设置:
可以在.config中设置CONFIG_HZ表示时钟中断的频率
1.时间比较
timer_after(a,b)、timer_before(a,b)…(其中a,b为jiffies)
为啥要用这些而不能直接比较?
避免了jiffies溢出导致判断失误(虽然不太容易溢出)
2.时间转换
1.ms与jiffies之间
如:jiffies_to_msecs
2.struct timespec/struct timeval与jiffies转换
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
如:timespec_to_jiffies、timeval_to_jiffies
3.可以通过clock_gettime获取此时jiffies对应的timespec
#include <time.h>
int clock_gettime(clockid_t clk_id, struct timespec
*res);
3.延时操作
1.长延时,进程调度方式(通过jiffies,默认HZ = 1000,也就是最多ms级别)
提供了msleep()、msleep_interrupt()
原理:(msleep为例)
msleep(1);
------------------------------------
set_current_state(TASK_UNINTERRUPT);//设置当前进程状态(为不可打断状态,只要进程不是TASK_RUNING状态就会从运行队列中删除。
//如果没有set_current_state那么当前进程仍处于runing状态,还是会得到cpu运行
schedule_timeout(jiffies+HZ);//运用了后面会讲到的定时器
2.短延时,忙等待方式
提供了mdelay(),udelay(),ndelay()
为啥要用忙等待:
1.短延时时间短,CPU忙等待可以容忍
2.系统中进程切换花费时间为几个到上百us,那么如果用调度方式可能延时时间到还在进程切换
4.内核定时器
1.定时器的管理:(基于per_cpu)
每个cpu中都会有一个tvec_base结构体数组管理注册的定时器
struct tvec_base {
spinlock_t lock;
struct timer_list *running_timer;
unsigned long timer_jiffies;
unsigned long next_timer;
unsigned long active_timers;
unsigned long all_timers;
int cpu;
struct tvec_root tv1;//tv1是管理定时时间为最近0-255个时钟周期的定时器
struct tvec tv2;
struct tvec tv3;
struct tvec tv4;
struct tvec tv5;
} ____cacheline_aligned;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QEzVbkEH-1679396761983)(C:\Users\cww\AppData\Roaming\Typora\typora-user-images\image-20221213210834738.png)]
如果有相同时间的定时器就用双链表挂起
2.如何实现
1.内核初始化
void __init init_timers(void)//被start_kernel调用
{
/* ensure there are enough low bits for flags in timer->base pointer */
BUILD_BUG_ON(__alignof__(struct tvec_base) & TIMER_FLAG_MASK);
init_timer_cpus();
init_timer_stats();
timer_register_cpu_notifier();
open_softirq(TIMER_SOFTIRQ, run_timer_softirq);//申请了软中断
}
在open_softirq(TIMER_SOFTIRQ, run_timer_softirq);这句有没有发现就像softirq_init中为tasklet做的注册action,只是定时器要自己注册。
2.驱动注册定时器
4.14之前:
1.init_timer(&timer)
timer.function = GetIntrTimerCallback;
2.void GetIntrTimerCallback(unsigned long devAddr)//中断调用
4.14之后:
1.timer_setup(&timer, GetIntrTimerCallback, 0);
//将定时器对象加入到当前cpu的tvec_base结构体数组中
3.调用栈
也就是每次内核定时器硬件中断都会在下半部执行 run_timer_softirq,然后在其中调用__run_timers。
这里面的操作就是遍历当前cpu中的tvec_base,如果找到了时间到了的定时器那么先将其从tvec_base中删除,然后再执行其注册函数。(所以定时器是一次性的,如果想重复使用必须在中断函数中再次注册)
static void run_timer_softirq(struct softirq_action *h)
{
struct tvec_base *base = __this_cpu_read(tvec_bases);
hrtimer_run_pending();
if (time_after_eq(jiffies, base->timer_jiffies))
__run_timers(base);
}
__run_timers://这里就会传入当前cpu的tvec_base然后遍历,注册的那个定时器时间到了就执行。
static inline void __run_timers(struct tvec_base *base)
{
struct timer_list *timer;
spin_lock_irq(&base->lock);
if (catchup_timer_jiffies(base)) {
spin_unlock_irq(&base->lock);
return;
}
while (time_after_eq(jiffies, base->timer_jiffies)) {
struct list_head work_list;
struct list_head *head = &work_list;
int index = base->timer_jiffies & TVR_MASK;
/*
* Cascade timers:
*/
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));
++base->timer_jiffies;
list_replace_init(base->tv1.vec + index, head);
while (!list_empty(head)) {
void (*fn)(unsigned long);
unsigned long data;
bool irqsafe;
timer = list_first_entry(head, struct timer_list,entry);
fn = timer->function;
data = timer->data;
irqsafe = tbase_get_irqsafe(timer->base);
timer_stats_account_timer(timer);
base->running_timer = timer;
detach_expired_timer(timer, base);
if (irqsafe) {
spin_unlock(&base->lock);
call_timer_fn(timer, fn, data);
spin_lock(&base->lock);
} else {
spin_unlock_irq(&base->lock);
call_timer_fn(timer, fn, data);
spin_lock_irq(&base->lock);
}
}
}
base->running_timer = NULL;
spin_unlock_irq(&base->lock);
}