linux 之时间子系统(八):hrtime 的实现机制

news2024/12/24 0:06:35

一、hrtimer 概述

在Linux内核中已经存在了一个管理定时器的通用框架。不过它也有很多不足,最大的问题是其精度不是很高。哪怕底层的定时事件设备精度再高,定时器层的分辨率只能达到Tick级别,按照内核配置选项的不同,在100Hz到1000Hz之间。但是,原有的定时器层由于实现教早,应用广泛,如果完全替换掉会引入大量代码改动。因此,Linux内核又独立设计出了一个叫高精度定时器层(High Resolution Timer)的框架,可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动程序。

高分辨率定时器是建立在每CPU私有独占的本地时钟事件设备上的,对于一个多处理器系统,如果只有全局的时钟事件设备,高分辨率定时器是无法工作的。因为如果没有每CPU私有独占的时钟事件设备,当到期中断发生时系统必须产生夸处理器中断来通知其它CPU完成相应的工作,而过多的夸处理器中断会带来很大的系统开销,这样会令使用高分辨率定时器的代价大大增加,还不如不用。为了让内核支持高分辨率定时器,必须要在编译的时候打开编译选项CONFIG_HIGH_RES_TIMERS。

高分辨率定时器层有两种工作模式:低精度模式与高精度模式。虽然高分辨率定时器子系统是为高精度定时器准备的,但是系统可能在运行过程中动态切换到不同精度和模式的定时事件设备,因此高精度定时器层必须能够在低精度模式与高精度模式下自由切换。

高分辨率定时器层使用红黑树来组织各个高分辨率定时器。随着系统的运行,高分辨率定时器不停地被创建和销毁,新的高分辨率定时器按顺序被插入到红黑树中,树的最左边的节点就是最快到期的定时器。

二、相关数据结构

2.1 高分辨率定时器由hrtimer结构体表示(代码位于include/linux/hrtimer.h中):
struct hrtimer {
	struct timerqueue_node		node;
	ktime_t				_softexpires;
	enum hrtimer_restart		(*function)(struct hrtimer *);
	struct hrtimer_clock_base	*base;
	u8				state;
	u8				is_rel;
	u8				is_soft;
	u8				is_hard;
};

 node:是一个timerqueue_node结构体变量。这个结构体中有两个成员,node是红黑树的节点,expires:表示该定时器的硬超时时间:

struct timerqueue_node {
    struct rb_node node;
    ktime_t expires;
};

 _softexpires:表示该定时器的软超时时间。高精度定时器一般都有一个到期的时间范围,而不像(低精度)定时器那样就是一个时间点。这个时间范围的前时间点就是软超时时间,而后一个时间点就是硬超时时间。达到软超时时间后,还可以再拖一会再调用超时回调函数,而到达硬超时时间后就不能再拖了。

function:定时器到期后的回调函数。

base:指向包含该高分辨率定时器的的hrtimer_clock_base结构体。

state:用来表示该高分辨率定时器当前所处的状态,目前共有两种状态:

/* 表示定时器还未激活 */
#define HRTIMER_STATE_INACTIVE	0x00
/* 表示定时器已激活(入列) */
#define HRTIMER_STATE_ENQUEUED	0x01

 is_rel:表示该定时器的到期时间是否是相对时间。

is_soft:表示该定时器是否是“软”定时器。
is_hard:表示该定时器是否是“硬”定时器。

2.2 hrtimer_clock_base
struct hrtimer_clock_base {
	struct hrtimer_cpu_base	*cpu_base;
	unsigned int		index;
	clockid_t		clockid;
	seqcount_t		seq;
	struct hrtimer		*running;
	struct timerqueue_head	active;
	ktime_t			(*get_time)(void);
	ktime_t			offset;
} __hrtimer_clock_base_align;

 cpu_base:指向所属CPU的hrtimer_cpu_base结构体。
index:表示该结构体在当前CPU的hrtimer_cpu_base结构体中clock_base数组中所处的下标。
clockid:表示当前时钟类型的ID值。
seq:顺序锁,在处理到期定时器的函数__run_hrtimer中会用到。
running:指向当前正在处理的那个定时器。
active:红黑树,包含了所有使用该时间类型的定时器。
get_time:是一个函数指针,指定了如何获取该时间类型的当前时间的函数。由于不同类型的时间在Linux中都是由时间维护层来统一管理的,因此这些函数都是在时间维护层里面定义好的。
offset:表示当前时间类型和单调时间之间的差值。

2.3 hrtimer_cpu_base

 每个CPU单独管理属于自己的高分辨率定时器,为了方便管理,专门定义了一个结构体hrtimer_cpu_base:

struct hrtimer_cpu_base {
	raw_spinlock_t			lock;
	unsigned int			cpu;
	unsigned int			active_bases;
	unsigned int			clock_was_set_seq;
	unsigned int			hres_active		: 1,
					in_hrtirq		: 1,
					hang_detected		: 1,
					softirq_activated       : 1;
#ifdef CONFIG_HIGH_RES_TIMERS
	unsigned int			nr_events;
	unsigned short			nr_retries;
	unsigned short			nr_hangs;
	unsigned int			max_hang_time;
#endif
#ifdef CONFIG_PREEMPT_RT
	......
#endif
	ktime_t				expires_next;
	struct hrtimer			*next_timer;
	ktime_t				softirq_expires_next;
	struct hrtimer			*softirq_next_timer;
	struct hrtimer_clock_base	clock_base[HRTIMER_MAX_CLOCK_BASES];
} ____cacheline_aligned;

lock:用来保护该结构体的自旋锁。

cpu:绑定到的CPU编号。

active_bases:表示clock_base数组中哪些元素下的红黑树中含有定时器。

clock_was_set_seq:表示时钟被设置的序数。

hres_active:表示是否已经处在了高精度模式下。

in_hrtirq:是否正在执行hrtimer_interrupt中断处理程序中。

hang_detected:表明在前一次执行hrtimer_interrupt中断处理程序的时候发生了错误。

softirq_activated:是否正在执行hrtimer_run_softirq软中断处理程序。

nr_events:表明一共执行了多少次hrtimer_interrupt中断处理程序。

nr_retries:表明在执行hrtimer_interrupt中断处理程序的时候对定时事件设备编程错误后重试的次数。

nr_hangs:表明在执行hrtimer_interrupt中断处理程序的时候发生错误的次数。

max_hang_time:表明在碰到错误后,在hrtimer_interrupt中断处理程序中停留的最长时间。

expires_next:该CPU上即将要到期定时器的到期时间。

next_timer:该CPU上即将要到期的定时器。

softirq_expires_next:该CPU上即将要到期的“软”定时器的到期时间。

softirq_next_timer:该CPU上即将要到期的“软”定时器。

clock_base:高分辨率定时器的到期时间可以基于以下几种时间类型,clock_base数组为每种时间基准系统都定义了一个hrtimer_clock_base结构体:

enum  hrtimer_base_type {
	HRTIMER_BASE_MONOTONIC,
	HRTIMER_BASE_REALTIME,
	HRTIMER_BASE_BOOTTIME,
	HRTIMER_BASE_TAI,
	HRTIMER_BASE_MONOTONIC_SOFT,
	HRTIMER_BASE_REALTIME_SOFT,
	HRTIMER_BASE_BOOTTIME_SOFT,
	HRTIMER_BASE_TAI_SOFT,
	HRTIMER_MAX_CLOCK_BASES,
};

没有加 SOFT 后缀的,表示是“硬”定时器,将直接在中断处理程序中处理;而加了SOFT后缀的,表示是“软”定时器,将在软中断(HRTIMER_SOFTIRQ)中处理。而且前面的一半都定义成“硬”定时器类型,后面一半都定义成“软”定时器类型,硬的一半和软的一半申明的次序也是对应的。这样设计就方便根据“硬”“软”特性和时间类型很快查出对应 hrtimer_clock_base 结构体的下标。

所以,综上所述,高分辨率定时器层的组织相对来说还是比较简单的,甚至比(低分辨率)定时器层还要简单。每个CPU对应有一个 hrtimer_cpu_base 的 Per CPU 结构体变量,其定义如下:

DEFINE_PER_CPU(struct hrtimer_cpu_base, hrtimer_bases) =
{
	.lock = __RAW_SPIN_LOCK_UNLOCKED(hrtimer_bases.lock),
	.clock_base =
	{
		{
			.index = HRTIMER_BASE_MONOTONIC,
			.clockid = CLOCK_MONOTONIC,
			.get_time = &ktime_get,
		},
		{
			.index = HRTIMER_BASE_REALTIME,
			.clockid = CLOCK_REALTIME,
			.get_time = &ktime_get_real,
		},
		{
			.index = HRTIMER_BASE_BOOTTIME,
			.clockid = CLOCK_BOOTTIME,
			.get_time = &ktime_get_boottime,
		},
		{
			.index = HRTIMER_BASE_TAI,
			.clockid = CLOCK_TAI,
			.get_time = &ktime_get_clocktai,
		},
		{
			.index = HRTIMER_BASE_MONOTONIC_SOFT,
			.clockid = CLOCK_MONOTONIC,
			.get_time = &ktime_get,
		},
		{
			.index = HRTIMER_BASE_REALTIME_SOFT,
			.clockid = CLOCK_REALTIME,
			.get_time = &ktime_get_real,
		},
		{
			.index = HRTIMER_BASE_BOOTTIME_SOFT,
			.clockid = CLOCK_BOOTTIME,
			.get_time = &ktime_get_boottime,
		},
		{
			.index = HRTIMER_BASE_TAI_SOFT,
			.clockid = CLOCK_TAI,
			.get_time = &ktime_get_clocktai,
		},
	}
};

每个 hrtimer_cpu_base 结构体中有一个 hrtimer_clock_base 类型的数组变量 clock_base,目前数组元素是8个,分别用来存放8种到期时间类型的高分辨率定时器。而每种到期时间类型下,又是以红黑数来组织所有的高分辨率定时器。因此,高分辨率定时器层的数据结构如下图所示:

三、高精度定时器相关API

3.1 高精度定时器层初始化 hrtimers_init

在linux内核启动初始化阶段,start_kernel 会调用 hrtimers_init 函数对高精度定时器层初始化:

void __init hrtimers_init(void)
{
        /* 初始化属于当前CPU的hrtimer_cpu_base结构体 */
	hrtimers_prepare_cpu(smp_processor_id());
        /* 打开HRTIMER_SOFTIRQ软中断 */
	open_softirq(HRTIMER_SOFTIRQ, hrtimer_run_softirq);
}

该函数主要功能就是初始化属于当前 CPU 的 hrtimer_cpu_base 结构体,然后打开HRTIMER_SOFTIRQ软中断。

int hrtimers_prepare_cpu(unsigned int cpu)
{
        /* 获得属于当前CPU的hrtimer_cpu_base结构体 */
	struct hrtimer_cpu_base *cpu_base = &per_cpu(hrtimer_bases, cpu);
	int i;
 
        /* 初始化所有的hrtimer_clock_base结构体 */
	for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++) {
                /* 初始化cpu_base */
		cpu_base->clock_base[i].cpu_base = cpu_base;
                /* 初始化红黑树 */
		timerqueue_init_head(&cpu_base->clock_base[i].active);
	}
 
	cpu_base->cpu = cpu;
	cpu_base->active_bases = 0;
	cpu_base->hres_active = 0;
	cpu_base->hang_detected = 0;
	cpu_base->next_timer = NULL;
	cpu_base->softirq_next_timer = NULL;
	cpu_base->expires_next = KTIME_MAX;
	cpu_base->softirq_expires_next = KTIME_MAX;
	hrtimer_cpu_base_init_expiry_lock(cpu_base);
	return 0;
}

 从上面的内核源码中,可以看出在内核初始化时,hrtimers_prepare_cpu 并没有对所有hrtimer_bases 初始化,而仅仅是对hrtimer_bases[0] 的8种hrtimer_clock_base 进行了初始化,红黑树初始化。那么有一个问题,其他CPU 的struct hrtimer_cpu_base 对象,什么时候开始初始化???

留一个问题:那么有一个问题,其他CPU 的struct hrtimer_cpu_base 对象,什么时候开始初始化??? 目前还没有找到源码中,是如何处理的;

3.2 定时器初始化 hrtimer_init

在将一个高分辨率定时器插入并激活之前,首先需要调用hrtimer_init函数对其进行初始化:

void hrtimer_init(struct hrtimer *timer, clockid_t clock_id,
		  enum hrtimer_mode mode)
{
	debug_init(timer, clock_id, mode);
	__hrtimer_init(timer, clock_id, mode);
}
EXPORT_SYMBOL_GPL(hrtimer_init);

clock_id:

<include/uapi/linux/time.h>
/*
 * The IDs of the various system clocks (for POSIX.1b interval timers):
 */
/* REALTIME记录的是从1970.1.1到当前的时间,常说的wall time(墙上时钟) */
#define CLOCK_REALTIME   0
/* MONOTIC记录的是从开机到当前的时间,suspend时不会增加 */
#define CLOCK_MONOTONIC   1 
#define CLOCK_PROCESS_CPUTIME_ID 2
#define CLOCK_THREAD_CPUTIME_ID  3
#define CLOCK_MONOTONIC_RAW  4
#define CLOCK_REALTIME_COARSE  5
#define CLOCK_MONOTONIC_COARSE  6
/* BOOTTIME记录的是从开机到当前的时间,suspend时依然会增加 */
#define CLOCK_BOOTTIME   7
#define CLOCK_REALTIME_ALARM  8
#define CLOCK_BOOTTIME_ALARM  9

 

其直接调用了__hrtimer_init函数:

static void __hrtimer_init(struct hrtimer *timer, clockid_t clock_id,
			   enum hrtimer_mode mode)
{
	bool softtimer = !!(mode & HRTIMER_MODE_SOFT);
	struct hrtimer_cpu_base *cpu_base;
	int base;
 
	if (IS_ENABLED(CONFIG_PREEMPT_RT) && !(mode & HRTIMER_MODE_HARD))
		softtimer = true;
 
        /* 将hrtimer结构体清0 */
	memset(timer, 0, sizeof(struct hrtimer));
 
        /* 获得当前CPU的hrtimer_cpu_base结构体变量 */
	cpu_base = raw_cpu_ptr(&hrtimer_bases);
 
	/* 相对的实时时间调整为单调时间 */
	if (clock_id == CLOCK_REALTIME && mode & HRTIMER_MODE_REL)
		clock_id = CLOCK_MONOTONIC;
 
        /* 根据定时器是不是“软”的找到hrtimer_clock_base的起始下标 */
	base = softtimer ? HRTIMER_MAX_CLOCK_BASES / 2 : 0;
        /* 通过clock_id找hrtimer_clock_base的偏移下标 */
	base += hrtimer_clockid_to_base(clock_id);
	timer->is_soft = softtimer;
	timer->is_hard = !softtimer;
	timer->base = &cpu_base->clock_base[base];
        /* 初始化定时器的红黑树节点结构体 */
	timerqueue_init(&timer->node);
}

所以,可以看出任何一个高分辨率定时器在初始化的时候都是默认被分配到当前处理器上的。

一个定时器驱动,在注册的时候,其实只是绑定在一个CPU 上,并不是所有的CPU 上都有。

 对于 clock_base 下标的计算,由于“硬”定时器模式和“软”定时器模式各占一半,上下对称。所以,如果是“软”的,那起始下标就是最大值的一半,否则就是0。然后,在根据 clock_id 找到对应模式的偏移下标,两者相加就可以了。

static inline int hrtimer_clockid_to_base(clockid_t clock_id)
{
	if (likely(clock_id < MAX_CLOCKS)) {
                /* 将clock_id转换成下标 */
		int base = hrtimer_clock_to_base_table[clock_id];
 
		if (likely(base != HRTIMER_MAX_CLOCK_BASES))
			return base;
	}
	WARN(1, "Invalid clockid %d. Using MONOTONIC\n", clock_id);
        /* 如果出错默认选择单调时间 */
	return HRTIMER_BASE_MONOTONIC;
}
static const int hrtimer_clock_to_base_table[MAX_CLOCKS] = {
	[0 ... MAX_CLOCKS - 1]	= HRTIMER_MAX_CLOCK_BASES,
 
	[CLOCK_REALTIME]	= HRTIMER_BASE_REALTIME,
	[CLOCK_MONOTONIC]	= HRTIMER_BASE_MONOTONIC,
	[CLOCK_BOOTTIME]	= HRTIMER_BASE_BOOTTIME,
	[CLOCK_TAI]		= HRTIMER_BASE_TAI,
};

 数组下标是 clock_id,而数组的值是要返回的高分辨率定时器的时间类型。由于实际有意义的只有4项,因此多出来的项全部填上 HRTIMER_MAX_CLOCK_BASES,表示出错了。

高分辨率定时器共有以下几种模式:

enum hrtimer_mode {
	HRTIMER_MODE_ABS	= 0x00,
	HRTIMER_MODE_REL	= 0x01,
	HRTIMER_MODE_PINNED	= 0x02,
	HRTIMER_MODE_SOFT	= 0x04,
	HRTIMER_MODE_HARD	= 0x08,
 
	HRTIMER_MODE_ABS_PINNED = HRTIMER_MODE_ABS | HRTIMER_MODE_PINNED,
	HRTIMER_MODE_REL_PINNED = HRTIMER_MODE_REL | HRTIMER_MODE_PINNED,
 
	HRTIMER_MODE_ABS_SOFT	= HRTIMER_MODE_ABS | HRTIMER_MODE_SOFT,
	HRTIMER_MODE_REL_SOFT	= HRTIMER_MODE_REL | HRTIMER_MODE_SOFT,
 
	HRTIMER_MODE_ABS_PINNED_SOFT = HRTIMER_MODE_ABS_PINNED | HRTIMER_MODE_SOFT,
	HRTIMER_MODE_REL_PINNED_SOFT = HRTIMER_MODE_REL_PINNED | HRTIMER_MODE_SOFT,
 
	HRTIMER_MODE_ABS_HARD	= HRTIMER_MODE_ABS | HRTIMER_MODE_HARD,
	HRTIMER_MODE_REL_HARD	= HRTIMER_MODE_REL | HRTIMER_MODE_HARD,
 
	HRTIMER_MODE_ABS_PINNED_HARD = HRTIMER_MODE_ABS_PINNED | HRTIMER_MODE_HARD,
	HRTIMER_MODE_REL_PINNED_HARD = HRTIMER_MODE_REL_PINNED | HRTIMER_MODE_HARD,
};

共有五种基本模式,其它模式都是由这五种组合而成:

  • HRTIMER_MODE_ABS:表示定时器到期时间是一个绝对值。
  • HRTIMER_MODE_REL:表示定时器到期时间是一个相对于当前时间之后的值。
  • HRTIMER_MODE_PINNED:表示定时器是否需要绑定到某个CPU上。
  • HRTIMER_MODE_SOFT:表示该定时器是否是“软”的,也就是定时器到期回调函数是在软中断下被执行的。
  • HRTIMER_MODE_HARD:表示该定时器是否是“硬”的,也就是定时器到期回调函数是在中断处理程序中被执行的。
3.3 定时器移除 remove_hrtimer

将一个高分辨率定时器从系统中移除是通过 remove_hrtimer 函数实现的:

static inline int
remove_hrtimer(struct hrtimer *timer, struct hrtimer_clock_base *base, bool restart)
{
	u8 state = timer->state;
 
        /* 如果定时器没有激活则直接返回 */
	if (state & HRTIMER_STATE_ENQUEUED) {
		int reprogram;
		
		debug_deactivate(timer);
                /* 只有要删除的定时器激活在当前处理器上时才需要重编程 */
		reprogram = base->cpu_base == this_cpu_ptr(&hrtimer_bases);
 
                /* 如果要删除的定时器不需要重新再激活则将其状态改为HRTIMER_STATE_INACTIVE */
		if (!restart)
			state = HRTIMER_STATE_INACTIVE;
 
		__remove_hrtimer(timer, base, state, reprogram);
		return 1;
	}
	return 0;
}

参数 base 指向的就是那个包含要删除定时器的 hrtimer_clock_base 结构体。参数 restart 表示该定时器删除后是不是还会马上被激活,如果是的话就没必要修改状态为HRTIMER_STATE_INACTIVE(未激活)了。该函数判断一些状态后,主要是调用__remove_hrtimer函数进行删除:

static void __remove_hrtimer(struct hrtimer *timer,
			     struct hrtimer_clock_base *base,
			     u8 newstate, int reprogram)
{
	struct hrtimer_cpu_base *cpu_base = base->cpu_base;
	u8 state = timer->state;
 
	/* 修改当前定时器的状态为参数指定的状态 */
	WRITE_ONCE(timer->state, newstate);
        /* 如果该定时器还没有被添加到任何红黑树中则直接返回 */
	if (!(state & HRTIMER_STATE_ENQUEUED))
		return;
 
        /* 将要删除的定时器从红黑树中移除 */
	if (!timerqueue_del(&base->active, &timer->node))
                /* 如果删除后红黑树为空则清除active_bases中对应的位 */
		cpu_base->active_bases &= ~(1 << base->index);
 
	/* 如果需要重编程并且要删除的定时器是其激活CPU上马上就要到期的定时器则重编程 */
	if (reprogram && timer == cpu_base->next_timer)
		hrtimer_force_reprogram(cpu_base, 1);
}

如果要删除的高分辨率定时器是其激活CPU上马上就要到期的那个定时器,则需要对底层的定时事件设备进行重新编程,让其在下一个定时器的到期时间上到期。关于重编程的部分,后面会介绍。

3.4 定时器激活 hrtimer_start_range_ns

hrtimer_start()/hrtimer_start_range_ns()开启定时器;如果定时器不需要指定到期范围就使用hrtimer_start(),如果定时器需要指定到期范围就使用hrtimer_start_range_ns()

要激活一个高分辨率定时器需要调用 hrtimer_start_range_ns 函数:

void hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
                u64 delta_ns, const enum hrtimer_mode mode)
{
    struct hrtimer_clock_base *base;
    unsigned long flags;
 
    if (!IS_ENABLED(CONFIG_PREEMPT_RT))
        WARN_ON_ONCE(!(mode & HRTIMER_MODE_SOFT) ^ !timer->is_soft);
    else
        WARN_ON_ONCE(!(mode & HRTIMER_MODE_HARD) ^ !timer->is_hard);
 
        /* 获得定时器对应CPU的hrtimer_cpu_base结构体内的自旋锁 */
    base = lock_hrtimer_base(timer, &flags);
 
        /* 激活定时器 */
    if (__hrtimer_start_range_ns(timer, tim, delta_ns, mode, base))
                /* 如果成功则尝试对定时事件设备重编程 */
        hrtimer_reprogram(timer, true);
 
        /* 释放自旋锁 */
    unlock_hrtimer_base(timer, &flags);
}
EXPORT_SYMBOL_GPL(hrtimer_start_range_ns);

参数 tim 保存了定时器的“软”到期时间。参数 delta_ns 是到期时间的范围,所以硬到期时间就是tim+delta_ns。参数 mode 指定了到期时间的类型。在获得了定时器对应 CPU 的hrtimer_cpu_base 结构体内的自旋锁后,其接着调用了 __hrtimer_start_range_ns 函数:

static int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
                    u64 delta_ns, const enum hrtimer_mode mode,
                    struct hrtimer_clock_base *base)
{
    struct hrtimer_clock_base *new_base;
 
    /* 先将该定时器从现有红黑树中移除 */
    remove_hrtimer(timer, base, true);
 
        /* 如果是相对时间则获得当前时间并累加得到绝对时间 */
    if (mode & HRTIMER_MODE_REL)
        tim = ktime_add_safe(tim, base->get_time());
 
    tim = hrtimer_update_lowres(timer, tim, mode);
 
        /* 设置定时器的“软”“硬”到期时间 */
    hrtimer_set_expires_range_ns(timer, tim, delta_ns);
 
    /* 尝试迁移该高分辨率定时器 */
    new_base = switch_hrtimer_base(timer, base, mode & HRTIMER_MODE_PINNED);
 
        /* 将定时器插入红黑树 */
    return enqueue_hrtimer(timer, new_base, mode);
}

该函数首先调用 remove_hrtimer 函数,如果要激活的定时器已经被激活过了的话,会将其先删除掉。由于后面还会再激活,所以对应的 restart 参数传的是 true。接着调用了hrtimer_set_expires_range_ns 函数,根据参数设置定时器的“软”“硬”到期时间:

static inline void hrtimer_set_expires_range_ns(struct hrtimer *timer, ktime_t time, u64 delta)
{
	timer->_softexpires = time;
	timer->node.expires = ktime_add_safe(time, ns_to_ktime(delta));
}

所以,“硬”到期时间就是“软”到期时间加上delta。

switch_hrtimer_base 函数会尝试迁移该要激活的高分辨率定时器,这个后面会分析。如果不需要迁移,则返回的 hrtimer_clock_base 结构体和调用参数是一样的,否则会返回一个新的要迁移到的 hrtimer_clock_base 结构体。

最后,函数调用 enqueue_hrtimer 函数,将定时器插入红黑树中,从而完成定时器的激活:

static int enqueue_hrtimer(struct hrtimer *timer,
			   struct hrtimer_clock_base *base,
			   enum hrtimer_mode mode)
{
	debug_activate(timer, mode);
 
        /* 设置active_bases中对应的位 */
	base->cpu_base->active_bases |= 1 << base->index;
 
	/* 更新定时器的状态为HRTIMER_STATE_ENQUEUED */
	WRITE_ONCE(timer->state, HRTIMER_STATE_ENQUEUED);
 
        /* 将该定时器加入对应hrtimer_clock_base结构体内的红黑树中 */
	return timerqueue_add(&base->active, &timer->node);
}

四、hrtimer 的到期处理

处理hrtimer的函数是__hrtimer_run_queues(),看看有谁调用该函数:

因此hrtimer的处理有3个入口:

  • 在hrtimer软中断处理程序中,只处理软timer;
  • 在低精度模式下,在每个jiffies的tick中断处理函数中执行,只处理硬timer;
  • 在高精度模式下,在每个clock_event_device的到期中断处理函数hrtimer_interrupt中执行,只处理硬timer。
 4.1 低精度模式

在系统运行早期,hrtimer子系统先运行在低精度模式,这时是在每个jiffies的tick事件中断(tick_handle_periodic())中处理到期的hrtimer,因此此时的时间精度和timer子系统是一致的,都是HZ级别的。

函数调用链:tick_handle_periodic()---->tick_periodic()---->update_process_times()---->run_local_timers()---->hrtimer_run_queues()
    
<kernel/time/hrtimer.c>
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);

    /* 如果当前时间 > 下一个到期的软timer的过期时间,表示有软timer到期了 */
 if (!ktime_before(now, cpu_base->softirq_expires_next)) {
        /* 将下个到期的软超时间设置为最大值,这算是默认值,等待hrtimer软中断处理函数重新更新下一个即将超时的软timer的过期时间*/
  cpu_base->softirq_expires_next = KTIME_MAX;
        /* 设置softirq_activated表示已经激活了本CPU的hrtimer软中断 */
  cpu_base->softirq_activated = 1;
        /* 触发HRTIMER软中断 */
  raise_softirq_irqoff(HRTIMER_SOFTIRQ);
 }
    
 /* 处理所有到期的硬timer,用户指定的hrtimer->function会在这里执行 */
 __hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_HARD);
 raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
}

低精度模式下,每次执行到hrtimer_run_queues都会检查是否符合切换到高精度的条件:

  • 高精度模式没有被禁止(打开了内核选项CONFIG_HIGH_RES_TIMERS);
  • 当前存在高分辨率的时钟源设备;
  • 当前定时设备是支持单次触发(oneshot)的。

当以上条件满足时,切换到高精度模式:

<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);

    /* 
     * 将clock_event_device的中断回调函数注册为hrtimer_interrupt(之前是tick_handle_periodic),
     * 同时时钟中断的触发模式从周期触发变成了oneshot模式
     */
 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;

    /* 
     * 因为时钟中断变成了oneshot模式,但是jiffies的更新、进程调度还是依赖于低精度的tick,
     * 因此专门通过一个hrtimer循环模拟低精度的tick
     */
 tick_setup_sched_timer();
 /* "Retrigger" the interrupt to get things going */
 retrigger_next_event(NULL);
}

我们再稍微关心一下模拟tick的hrtimer的回调函数: 

<kernel/time/tick-sched.c>
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();

    /* 主要是更新jiffies时间,注意smp系统中只有一个cpu负责更新jiffiies */
 tick_sched_do_timer(ts, 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的到期时间推后tick_period时间 */
 hrtimer_forward(timer, now, tick_period);

    /* 返回HRTIMER_RESTART表示该hrtimer需要再次执行,这就实现了每tick_period时间触发一次tick中断了 */
 return HRTIMER_RESTART;
}
 4.2 高精度模式

当系统切换到高精度定时器模式后,某个hrtimer到期后会调用中断处理函数hrtimer_interrupt()。需要强调的是,即使在开了PREEMPT_RT的系统上,hrtimer_interrupt也是在硬中断上下文中执行

<kernel/time/hrtimer.c>
/*
 * High resolution timer interrupt
 * Called with interrupts disabled
 */
void hrtimer_interrupt(struct clock_event_device *dev)
{
 struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
 ktime_t expires_next, now, entry_time, delta;
 unsigned long flags;
 int retries = 0;

    /* 如果当前不是高精度定时器模式,则触发oops使系统panic */
 BUG_ON(!cpu_base->hres_active);
 cpu_base->nr_events++;
 dev->next_event = KTIME_MAX;

 raw_spin_lock_irqsave(&cpu_base->lock, flags);
    /* 获得当前时间 */
 entry_time = now = hrtimer_update_base(cpu_base);
retry:
    /* 标识当前处于hrtimer_interrupt函数中 */
 cpu_base->in_hrtirq = 1;
 /*
  * We set expires_next to KTIME_MAX here with cpu_base->lock
  * held to prevent that a timer is enqueued in our queue via
  * the migration code. This does not affect enqueueing of
  * timers which run their callback and need to be requeued on
  * this CPU.
  */
 cpu_base->expires_next = KTIME_MAX;

    /* 如果当前时间 > 下一个到期的软timer的过期时间,表示有软timer到期了 */
 if (!ktime_before(now, cpu_base->softirq_expires_next)) {
  /* 将下个到期的软超时间设置为最大值,这算是默认值,等待hrtimer软中断处理函数重新更新下一个即将超时的软timer的过期时间*/
        cpu_base->softirq_expires_next = KTIME_MAX;
        /* 设置softirq_activated表示已经激活了本CPU的hrtimer软中断 */
  cpu_base->softirq_activated = 1;
        /* 触发HRTIMER软中断 */
  raise_softirq_irqoff(HRTIMER_SOFTIRQ);
 }

    /* 处理所有到期的硬timer,用户指定的hrtimer->function会在这里执行 */
 __hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_HARD);

    /* 更新接下来即将到期的定时器 */
 /* Reevaluate the clock bases for the next expiry */
 expires_next = __hrtimer_get_next_event(cpu_base, HRTIMER_ACTIVE_ALL);
 /*
  * Store the new expiry value so the migration code can verify
  * against it.
  */
 cpu_base->expires_next = expires_next;
 cpu_base->in_hrtirq = 0;
 raw_spin_unlock_irqrestore(&cpu_base->lock, flags);

    /* 使用最新的超时时间对定时事件设备(clock_event_device)进行重新编程,即指定下一次arch_timer的中断什么时候到来 */
 /* Reprogramming necessary ? */
 if (!tick_program_event(expires_next, 0)) {
  cpu_base->hang_detected = 0;
  return;
 }

 /*
  * The next timer was already expired due to:
  * - tracing
  * - long lasting callbacks
  * - being scheduled away when running in a VM
  *
  * We need to prevent that we loop forever in the hrtimer
  * interrupt routine. We give it 3 attempts to avoid
  * overreacting on some spurious event.
  *
  * Acquire base lock for updating the offsets and retrieving
  * the current time.
  */
 raw_spin_lock_irqsave(&cpu_base->lock, flags);
    /* 重新获取当前时间,retry处理定时器 & 对定时事件设备进行编程 */
 now = hrtimer_update_base(cpu_base);
 cpu_base->nr_retries++;
 if (++retries < 3)
  goto retry;
 /*
  * Give the system a chance to do something else than looping
  * here. We stored the entry time, so we know exactly how long
  * we spent here. We schedule the next event this amount of
  * time away.
  */
    /* 如果走到这里说明3次对定时事件设备编程都失败了,更新与hang有关的信息统计 */
 cpu_base->nr_hangs++;
 cpu_base->hang_detected = 1;
 raw_spin_unlock_irqrestore(&cpu_base->lock, flags);

    /* 计算在hrtimer_interrupt中花费了多少时间,更新max_hang_time */
 delta = ktime_sub(now, entry_time);
 if ((unsigned int)delta > cpu_base->max_hang_time)
  cpu_base->max_hang_time = (unsigned int) delta;
 /*
  * Limit it to a sensible value as we enforce a longer
  * delay. Give the CPU at least 100ms to catch up.
  */
    /* 适当延后下一次到期时间,最多100毫秒 */
 if (delta > 100 * NSEC_PER_MSEC)
  expires_next = ktime_add_ns(now, 100 * NSEC_PER_MSEC);
 else
  expires_next = ktime_add(now, delta);
 /* 强制对定时事件设备进程重新编程,否则收不到arch_timer中断了 */
    tick_program_event(expires_next, 1);
 pr_warn_once("hrtimer: interrupt took %llu ns\n", ktime_to_ns(delta));
}
 4.3 软中断中处理软hrtimer

前文在低精度模式/高精度模式中,在硬中断上下文中处理了硬timer们,对于软timer只是raise了HRTIMER的软中断。这里只聊HRTIMER软中断处理函数。

<kernel/time/hrtimer.c>
static __latent_entropy void hrtimer_run_softirq(struct softirq_action *h)
{
 struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases);
 unsigned long flags;
 ktime_t now;

 hrtimer_cpu_base_lock_expiry(cpu_base);
 raw_spin_lock_irqsave(&cpu_base->lock, flags);

    /* 获得当前时间 */
 now = hrtimer_update_base(cpu_base);
    /* 处理所有到期的软timer,用户指定的hrtimer->function会在这里执行 */
 __hrtimer_run_queues(cpu_base, now, flags, HRTIMER_ACTIVE_SOFT);

    /* 将本cpu的softirq_activated状态位置0,表示没有激活HRTIMER的软中断 */
 cpu_base->softirq_activated = 0;
 hrtimer_update_softirq_timer(cpu_base, true);

    /* 
     * 更新即将到期的软timer,对clock_event_device重新编程,
     * 同时更新cpu_base->softirq_expires_next,将其从在
     * hrtimer_interrupt中置的KTIME_MAX更新为下一个到期的软timer的时间
     */
 raw_spin_unlock_irqrestore(&cpu_base->lock, flags);
 hrtimer_cpu_base_unlock_expiry(cpu_base);
}

 结合hrtimer_run_softirq()hrtimer_interrupt()对软中断的处理,可以看出如果某一次HRTIMER的软中断处理函数hrtimer_run_softirq()因为操作不当没有得到执行,那么所有的软hrtimer将永远不会得到执行,因此需要确保软中断子系统是稳定的。笔者在生产环境中遇到过网络驱动程序在未关中断的情况下调用raise_softirq_irqoff(NETRX_SOFTIRQ)修改软中断的pending位,此时正好来了arch_timer的中断,从而触发hrtimer_interrupt调用raise_softirq_irqoff(HRTIMER_SOFTIRQ)也来修改软中断的pending位;这种对全局变量非原子的操作,会导致变量RMW(Read-Modify-Write)过程中出现竞争,从而丢掉HRTIMER_SOFTIRQ的pending置位,在高频网络收发包情况下会必现软timer超时,且永远得不到处理的情况,因此当前对软timer的处理逻辑不得不说是十分脆弱的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1937680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【性能评估工具】—— SLAM性能评估工具evo的安装与常用指令的详细介绍

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、evo的安装1. 直接指令安装2. 换下载源进行安装 二、evo的使用1. 常见的数据集格式介绍3. 数据格式转换4. evo工具常用命令介绍5. 指令命令的使用 三、常用指…

科技赋能,智慧粮仓视频综合管理方案助力粮食安全

一、背景需求 随着科技的快速发展&#xff0c;智慧化、智能化管理已成为各行各业的重要发展方向。粮食仓储作为国家粮食安全战略的重要组成部分&#xff0c;其管理的科学性和智能化水平直接关系到粮食的存储安全、品质保障和运营效率。 因此&#xff0c;TSINGSEE青犀提出一套…

Agilent 安捷伦 DSO90804A 高性能示波器

Agilent 安捷伦 DSO90804A 高性能示波器 DSO90804A Infiniium 高性能示波器&#xff1a;8 GHz 8 GHz4个模拟通道高达 1 Gpts 存储器和 40 GSa/s 采样率可以提供更完整的信号迹线捕获50 mV/格时低至 1.15 mVrms 的本底噪声和深入的抖动分析功能可以确保卓越的测量精度硬件加速…

B3636 源代码

快速直达专线 原文 题解没给代码&#xff0c;所以这里给一下 #include<bits/stdc.h> using namespace std; int f[10000007]; int main(){int n;cin>>n;//int cab;f[1]0;for(int i2;i<n5;i){if(i%20)f[i]min(f[i-1]1,f[i/2]1);//是偶数都有可能else f[i]f[i-1…

如何使用简鹿水印助手或 Photoshop 给照片添加文字

在社交媒体中&#xff0c;为照片添加个性化的文字已经成为了一种流行趋势。无论是添加注释、引用名言还是表达情感&#xff0c;文字都能够为图片增添额外的意义和风格。本篇文章将使用“简鹿水印助手”和“Adobe Photoshop”这两种工具给照片添加文字的详细步骤。 使用简鹿水印…

c++信号和槽机制的轻量级实现,sigslot 库介绍及使用

Qt中的信号与槽机制很好用&#xff0c;然而只在Qt环境中。在现代 C 编程中&#xff0c;对象间的通信是一个核心问题。为了解决这个问题&#xff0c;许多库提供了信号和槽&#xff08;Signals and Slots&#xff09;机制。今天推荐分享一个轻量级的实现&#xff1a;sigslot 库。…

bootstrap-datetimepicker设置时分

bootstrap-datetimepicker设置时分 需求背景时分年月日 需求背景 在日常工作中遇到一个业务场景&#xff0c;需要时间控件来选择时分&#xff0c;但是不需要年月日的成分&#xff0c;实现之后的效果如图 那么下面就开始查找相关的时间控件插件&#xff0c;这里示例图中用到的…

9.11和9.9哪个大?

没问题 文心一言 通义千问

make2s2o:自动编译汇编

模板Makefile&#xff0c;编译多个C/C模块成平台相关的汇编码与目标码。

Linux先行一步

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…

十、操作符详解

目录 1、操作符分类 2、二进制转换 2.1二进制转十进制 2.1.1、十进制转二进制 2.2、二进制转八进制和十六进制 2.2.1、二进制转八进制 2.2.2、二进制转十六进制 3、原码、反码、补码 4、移位操作符&#xff08;移动的是二进制位&#xff09; 4.1、左移操作符 4.2、右…

vue3中provide 和 inject 用法#Vue3中解决局部刷新问题

vue3中provide 和 inject 用法#Vue3中解决局部刷新问题 在父子组件传递数据时&#xff0c;通常使用的是 props 和 emit&#xff0c;父传子时&#xff0c;使用的是 props&#xff0c;如果是父组件传孙组件时&#xff0c;就需要先传给子组件&#xff0c;子组件再传给孙组件&…

前端如何支持i18n?

何为i18n&#xff1f; 系统支持多语言的功能称之为国际化&#xff0c;英文为 internationalization 一共18个字母&#xff0c;简称i18n。随机近些年国内市场饱和&#xff0c;各厂商纷纷出海&#xff0c;i18n成了必要的能力。 如何做&#xff1f; 简单介绍下思路&#xff0c;就…

C语言两数相除(要求只能使用加法和减法)求商和余数

思路分析&#xff1a;举个例子如 8/3 2余2 8-35 5-32 减了2次 最后一次结果为2 9/33 9-36 6-33 3-30 减了3次 最后一次为0 10/33余1 10-37 7-34 4-31 减了3次 最后一次为1 所以可以得出规律为…

huawei USG6001v1学习---防火墙相关知识(2)

目录 1.安全策略 2.防火墙的状态检测和会话表技术 3.FTP 4.用户认证 5.认证策略 1.安全策略 传统包过滤技术 --- 其本质就是ACL访问控制列表&#xff0c;根据数据包的特征进行过滤&#xff0c;对比规则&#xff0c; 执行对应的动作&#xff1b; 这里数据包的特征 --- …

Nest.js 实战 (二):如何使用 Prisma 和连接 PostgreSQL 数据库

什么是 Prisma? Prisma 是一个开源的下一代 ORM。它包含了以下部分&#xff1a; Prisma Client: 自动生成、类型安全的查询构建器&#xff0c;用于 Node.js 和 TypeScriptPrisma Migrate: 数据迁移系统Prisma Studio: 查询和编辑数据库中数据的图形化界面 Prisma 客户端可以…

Java面试(持续更新)

Redis使用场景 缓存穿透 当有该数据的时候&#xff0c;redis中的数据已经是原来数据的null值了&#xff0c;可能会出现不一致的问题。 缓存击穿 跟钱相关的强一致用互斥锁。 用户高体验用逻辑过期。 缓存雪崩 ttl随机值 mysql于redis保持数据同步 Redis持久化问题 RDB AOF R…

复习知识点java

目录 1.题目分析&#xff1a;2.思考题3.题目&#xff1a;4.题目&#xff1a;求X的反码和补码计算反码计算补码 计算2乘以8的结果实现两个整数变量的交换异或运算符&#xff08;^&#xff09;的特点 1.题目 byte b13,b24,b; bb1b2; b34; 哪句是编译失败的呢&#xff1f;为什么呢…

【人工智能】机器学习 -- 决策树(乳腺肿瘤数)

目录 一、使用Python开发工具&#xff0c;运行对iris数据进行分类的例子程序dtree.py&#xff0c;熟悉sklearn机器实习开源库。 二、登录https://archive-beta.ics.uci.edu/ 三、使用sklearn机器学习开源库&#xff0c;使用决策树对breast-cancer-wisconsin.data进行分类。 …

系统架构师考点--统一建模语言UML

大家好。今天我来总结一下面向对象的第二个考点–统一建模语言UML。 UML(统一建模语言)是一种可视化的建模语言&#xff0c;而非程序设计语言&#xff0c;支持从需求分析开始的软件开发的全过程。UML的结构包括构造块、规则和公共机制三个部分。其中考点主要集中在构造块部分&…