大家好,我是情报小哥~
在Linux早期多核支持阶段,存在一种主核(boot CPU)通过定时器中断来通知从核(其他CPU)的方式。这种方式主要用于多核间的通信和任务调度。
采用的方法之一是使用IPI(中断处理器间中断)进行核间通信。主核通过定时器中断来触发IPI,从而通知从核进行相应的处理。
大概是这样一个步骤:
主核初始化一个定时器,设置定时器中断处理函数。
在定时器中断处理函数中,主核向从核发送IPI。
从核收到IPI后,执行相应的中断处理函数。

// 主核发送IPIvoidarch_send_call_function_ipi_mask(const struct cpumask *mask){ send_IPI_mask(mask, CALL_FUNCTION_VECTOR);}// 从核处理IPI__visible void __irq_entry smp_reschedule_interrupt(struct pt_regs *regs){ ack_APIC_irq(); scheduler_ipi();}但是这样会存在一些问题,在早期SMP系统中,由于硬件限制和设计简化,可能只有一个全局的定时器,由主核(通常是CPU0)负责处理,然后通过IPI(处理器间中断)通知其他从核。这种方式存在以下问题:
可扩展性差:随着CPU数量增加,主核的负担加重。
延迟高:从核的时钟中断依赖于主核的定时器周期和IPI传递。
功耗问题:从核在空闲时可能仍然需要响应IPI,无法进入深度的休眠状态
现代Linux内核采用了高度分布式的每CPU定时器架构,每个CPU都有独立的定时器管理和调度能力。这种设计显著提高了多核系统的性能和可扩展性。 在系统启动时,每个CPU都会初始化自己的时钟事件设备。
Linux内核将定时器抽象为时钟事件设备,用struct clock_event_device表示。每个CPU都有自己的时钟事件设备,它负责在指定时间产生中断。
structclock_event_device {void (*event_handler)(struct clock_event_device *);int (*set_next_event)(unsignedlong evt, struct clock_event_device *);/* ... 其他字段 ... */unsignedlong mult;unsignedlong shift; u64 min_delta_ns; u64 max_delta_ns;constchar *name;int rating;int irq;cpumask_t cpumask;structlist_headlist;} ____cacheline_aligned;在系统启动时,每个CPU都会初始化自己的时钟事件设备。每个CPU的时钟中断处理函数通常是tick_handle_periodic(对于周期性模式)或tick_handle_oneshot(对于单次触发模式)。在中断处理函数中,会调用update_process_times来更新进程时间、处理定时器软中断等。
// 周期性时钟中断处理函数voidtick_handle_periodic(struct clock_event_device *dev){// 获取当前CPU的编号int cpu = smp_processor_id();// 更新jiffies(全局时间)并处理时间更新 tick_periodic(cpu);// 如果该时钟事件设备是周期性的,则重新设置下一次中断if (dev->mode == CLOCK_EVT_MODE_PERIODIC) dev->set_next_event(dev->next_event, dev);}// 在tick_periodic中,会调用update_process_timesvoidupdate_process_times(int user_tick){structtask_struct *p = current;// 更新进程运行时间 account_process_tick(p, user_tick);// 触发定时器软中断 run_local_timers(); rcu_check_callbacks(cpu, user_tick);// 调度器tick scheduler_tick();// 计算负载等 calc_global_load();}每个CPU通过自己的定时器中断触发调度器tick,从而进行进程调度、负载均衡等操作。
voidscheduler_tick(void){int cpu = smp_processor_id();structrq *rq = cpu_rq(cpu);structtask_struct *curr = rq->curr;// 更新运行时间 update_rq_clock(rq); curr->sched_class->task_tick(rq, curr, 0);// 触发负载均衡,但通常不是每个tick都进行,而是间隔进行 trigger_load_balance(rq);}现代Linux内核为每个CPU配备独立的定时器,非常多的好处,每个CPU独立处理自己的定时中断,不会随着CPU数量增加而产生瓶颈。而且每个CPU可以独立设置定时器,无需等待主核的通知,响应更及时。
在Linux中,尽管每个核心都有自己的定时器硬件和定时器中断,但系统时间(也称为墙上时间)是全局的,由内核维护一个统一的系统时间。这个系统时间通常基于一个全局的时钟源(例如时钟芯片,如HPET、ACPI PM Timer等)来更新。
然而,在多核系统中,每个核心可能会独立读取本地时钟源(如TSC,时间戳计数器)来获取当前时间,但由于每个核心的TSC可能不同步,因此内核需要做一些工作来保证时间的一致性。
在系统启动时,会选择一个全局的时钟源,并初始化一个全局的时钟事件设备(可能由某个核心负责,或者使用广播机制)。
每个核心在启动时,会初始化自己的时钟事件设备,并将其注册到时钟事件框架中。
系统时间通常由全局时钟源和时钟事件设备来更新。在早期的内核中,可能由一个核心(如引导核心)负责更新系统时间,而其他核心则通过读取全局变量来获取系统时间。但是,这样可能会导致其他核心读取到过时的系统时间,因此需要采用一些同步机制。
在现代Linux内核中,使用了更复杂的方法来保证多核间时间的同步。例如,每个核心在读取时间时,会综合考虑全局时钟源和本地的偏移。内核会维护一个每CPU变量,记录该CPU与全局系统时间之间的偏移(或者记录每个CPU的TSC与全局时间的对应关系),考虑当前CPU的偏移,从而返回一个一致的系统时间。

END
来源:嵌入式情报局