本文所有代码分析均基于linux-5.4.18
1.  时钟节拍tick
        时钟节拍tick是一个逻辑概念,它本身和时间没有关系,是linux系统周期性时钟的一次中断,仅表示系统的一次嘀嗒。在SMP系统中,linux内核会为每个cpu core创建一个tick device,它的实现基于clock event device ,struct tick_device结构体两个成员,其中一个就是clock event device。
        tick为周期性时钟的一次中断,linux内核支持两种时钟,一种为传统时钟,精度较低,另外一种为高精度时钟,精度为ns级。早期linux只支持传统时钟,在2.6.16以后加入了高精度时钟的支持。目前两种时钟都在使用,在内核启动时,tick首先默认使用传统时钟,然后会切换到高精度模式。
        linux内核中的tick周期为tick_period = NSEC_PER_SEC / HZ,即tick = 1s/HZ,表示系统里每个tick间隔为1/HZ秒,HZ这个参数,由内核的编译配置项CONFIG_HZ指定,取值可以为100、250、300、1000。
        tick的计数用jiffies变量表示,jiffies变量是一个计数器,用来记录系统启动以来产生的节拍总数。tick每发生一次,它便加“1”。
        这里多说一句,linux的时钟tick对preempt rt linux实时性影响不大,缩短tick间隔,并不会对微秒级的实时性有所提高。
1.1 start_kernel阶段的一些时钟相关初始化(可另做研究)
init/mian.c
        start_kernel()
                --> init_IRQ();
                --> tick_init()
                        --> tick_broadcast_init();
                        --> tick_nohz_init();
                --> init_timers();
                --> hrtimers_init();
                        --> hrtimers_prepare_cpu(smp_processor_id());
                        --> open_softirq(HRTIMER_SOFTIRQ, hrtimer_run_softirq);
                --> softirq_init();
                --> time_init();
                --> sched_clock_init();
1.2 时钟节拍tick实现
        arm架构设备上,linux内核中tick的实现入口在arm_arch_timer.c中
        inux内核启动时,首先会申请arch_timer中断,中断处理函数中直接执行clock event device的event_handler。然后会在cpu up阶段,为每个cpu core注册一个clock event设备arch_timer_evt,注册时会创建一个tick device(tick_cpu_device)。当tick发生,触发arch_timer中断,在arch_timer中断处理中在,执行clock event 设备arch_timer_evt的event_handler。
        linux内核启动初期,tick使用传统时钟实现,这时在arch_timer_evt的event_handler中,尝试切换到高精度模式和执行其他工作:jiffies计数加1、更新墙上时间、scheduler_tick()等。
        tick的整个实现过程如下:
  1. 在arch_timer驱动中申请arch_timer中断,并注册中断处理函数。在中断处理函数中,完成clock event设备arch_timer_evt的event_handler(tick_handle_periodic/hrtimer_interrupt)的执行
  2. 在arch_timer中设置cpu up过程中CPUHP_AP_ARM_ARCH_TIMER_STARTING阶段的回调函数arch_timer_starting_cpu,在cpu up对应阶段调用。
  3. 在每个cpu core的cpu up过程的对应阶段,执行arch_timer_starting_cpu,在其中会进行新的clock event设备arch_timer_evt的配置和注册,注册arch_timer_evt的时,还有以下几个工作:
    • 完成arch_timer_evt设备所在tick device的创建,当前mode为TICKDEV_MODE_PERIODIC。
    • 设置arch_timer_evt的event_handler为tick周期处理函数tick_handle_periodic(),此时内核使用传统时钟,所以tick_handle_periodic()为传统时钟下arch_timer_evt的event_handler
  4. 当arch_timer中断触发后,执行clock event 设备arch_timer_evt的event_handler,即tick_handler_periodic()。主要工作有:jiffies计数加“1”、更新墙上时间、尝试切换到高精度模式以及scheduler_tick()的执行。
  5. 其中尝试切换到高精度模式以及scheduler_tick()的执行,通过调用update_process_times()完成。
  6. update_process_times()首先会判断是否已经切到了高精度模式,如果是直接返回,然后执行scheduler_tick()。如果没有切换到高精度模式,尝试切换到高精度模式后,在执行scheduler_tick()。
  7. 如果没有切换到高精度模式前,判断当前满足切换条件,调用hrtimer_switch_to_hres,尝试切换到高精度模式。如果不满足切换条件,则在__hrtimer_run_queues中通过while循环查询是否有到期定时器,如果有,通过__run_hrtimer执行其处理函数。hrtimer_switch_to_hres()切换到高精度模式的工作主要如下:
    • 通过tick_init_highres() 间接调用tick_switch_to_oneshot(hrtimer_interrupt),设置高精度模式下,clock event设备arch_timer_evt的event_handler,从之前的tick_handle_periodic改为hrtimer_interrupt。切换到高精度模式后,arch_timer中断到来后,便不再执行tick_handle_periodic(),而是改为hrtimer_interrupt()。
    • 调用tick_setup_sched_timer() 完成高精度定时器sched_timer的初始化和启动。周期tick_period = NSEC_PER_SEC / HZ,超时处理函数为tick_sched_timer。
  8. 在切换到高精度模式后,每次tick到来,即arch_timer中断到来后,执行clock event设备arch_timer_evt的event_handler,此时为hrtimer_interrupt()。在hrtimer_interrupt()判断并执行sched_timer的超时处理函数tick_sched_timer()。
  9. tick_sched_timer()主要工作如下:
    • tick_do_update_jiffies64()更新jiffies计数及墙上时间
    • 通过tick_sched_handle()的调用执行update_process_times(),详见步骤6,由于此时在高精度模式下,所以直接执行scheduler_tick();
    • hrtimer_forward(timer, now, tick_period);重新推迟sched_timer定时器,时间间隔为tick_period;
    • 返回HRTIMER_RESTART;
  10. sched_timer超时后,arch_timer中断触发,返回步骤8。
1.2.1 arch timer设备
        linux内核时钟节拍tick,通过clock event devices实现。linux内核启动时,在每个cpu code的cpu up的CPUHP_AP_ARM_ARCH_TIMER_STARTING阶段,为当前cpu core注册clock event device(arch_timer_evt)并创建tick device(tick_cpu_device)。
        linux内核时钟节拍tick,其实就是arch_timer的一次中断,在内核启动初期,tick使用传统时钟,然后会尝试切换到高精度时钟
        arch_timer设备的一些工作:
  1. 为每个cpu core申请arch_timer中断,并为其注册中断处理函数,在中断处理函数中会执行clock event设备的event_handler,比如时钟tick所在的clock event设备arch_timer_evt的event_handler
  2. 设置cpu up过程中,CPUHP_AP_ARM_ARCH_TIMER_STARTING阶段的回调函数arch_timer_starting_cpu(),在cpu up的对应阶段调用。clock event设备arch_timer_evt的注册以及tick设备tick_cpu_device的创建都在回调函数arch_timer_starting_cpu()完成。
  3. 使用clocksource_register_hz()注册一个新的时钟源clocksource_counter,启动高精度定时器sched_clock_timer等等。
drivers/clocksource/arm_arch_timer.c
    /* arm时钟源驱动的入口函数,根据设备树或者acpi分别采用不同的入口 */
    arch_timer_of_init() / arch_timer_acpi_init()
        --> arch_timer_register()
            /*
             * 根据arch_timer_uses_ppi模式,为每个cpu core申请arch_timer中断,
             * 中断处理中的操作:
             * 读取arch timer控制寄存器ARCH_TIMER_REG_CTRL的值
             * 如果发现STAT位被置位,清楚此位
             * 执行对应clock event设备的event_handler()
             */
            --> request_percpu_irq(ppi, arch_timer_handler_phys,
                         "arch_timer", arch_timer_evt);
            --> arch_timer_cpu_pm_init()
            /*
             * 设置CPU状态机中CPUHP_AP_ARM_ARCH_TIMER_STARTING状态的回调函数
             * 在cpu up对应阶段进行调用。
             * 回调函数中完成clock event设备(arch_timer_evt)的注册
             * 和tick device(tick_cpu_device)的创建。
             */
            --> cpuhp_setup_state(CPUHP_AP_ARM_ARCH_TIMER_STARTING,
                        "clockevents/arm/arch_timer:starting",
                        arch_timer_starting_cpu, arch_timer_dying_cpu);
        --> arch_timer_common_init()
            --> arch_timer_banner(arch_timers_present);
            --> arch_counter_register(arch_timers_present);
                /* 注册新的时钟源clocksource_counter */
                --> clocksource_register_hz(&clocksource_counter, arch_timer_rate);
                --> timecounter_init(&arch_timer_kvm_info.timecounter, &cyclecounter, start_count);
                /* 主要用来启动sched_clock_timer,用来更新sched clock,以供sched_clock()方法使用 */
                --> sched_clock_register(arch_timer_read_counter, 56, arch_timer_rate);
            --> arch_timer_arch_init()
        arch_timer的中断处理函数arch_timer_handler_phys()中直接调用timer_handler()完成以下工作:
  • 首先读取ARCH_TIMER_REG_CTRL寄存器的值
  • 如果ARCH_TIMER_CTRL_IT_STAT被置位,清楚标志位,
  • 最后执行对应clock event设备的event_handler
drivers/clocksource/arm_arch_timer.c
timer_handler(ARCH_TIMER_PHYS_ACCESS, evt)
{
    /* 读取寄存器ARCH_TIMER_REG_CTRL值 */
    ctrl = arch_timer_reg_read(access, ARCH_TIMER_REG_CTRL, evt);
    if (ctrl & ARCH_TIMER_CTRL_IT_STAT) {
        ctrl |= ARCH_TIMER_CTRL_IT_MASK;
        arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl, evt);
        /* 执行对应clock event设备的event_handler() */
        evt->event_handler(evt);
        return IRQ_HANDLED;
    }
    return IRQ_NONE;
}
        以上是arm架构上arch_timer设备的初始化和简单逻辑,本文主要介绍时钟节拍tick,其相关实现在cpu up过程CPUHP_AP_ARM_ARCH_TIMER_STARTING阶段的回调函数中arch_timer_starting_cpu()实现。
1.2.2  clock event设备的注册和tick device的创建
        在linux内核启动过程中,会对CPU核进行唤醒动作,这个过程每个CPU核会经过几个不同的阶段,其中包括CPUHP_AP_ARM_ARCH_TIMER_STARTING(包含在CPUHP_AP_OFFLIEN至CPUHP_AP_ONLINE阶段中,参考CPUHOTPLUG),为CPU核上的arch_timer启动阶段,这个工作通过执行状态对应的回调函数arch_timer_starting_cpu()完成。
        这个过程中会注册一个clock event设备arch_timer_evt,然后通过tick_setup_device()完成tick设备tick_cpu_device的创建。tick设备结构体struct tick_device中只有两个成员变量,其中一个便为clock event device。
struct tick_device {
    struct clock_event_device *evtdev;
    enum tick_device_mode mode;
};
        配置注册clock event设备以及tick设备的创建流程如下:
drivers/clocksource/arm_arch_timer.c
    arch_timer_starting_cpu()
        --> __arch_timer_setup(ARCH_TIMER_TYPE_CP15, clk);
               /* 
                * 配置并注册clock event设备arch_timer_evt
                * 然后set up the tick device
                */
            --> clockevents_config_and_register();
        --> enable_percpu_irq()
        --> arch_counter_set_user_access();
        clockevents_config_and_register()中配置并注册clock event 设备,然后执行tick_check_new_device()-->tick_setup_device()进行tick device创建
kernel/time/clockevents.c
    clockevents_config_and_register()
        --> clockevents_config()
            /* register the clock event device */
        --> clockevents_register_device(struct clock_event_device *dev)
            --> list_add(&dev->list, &clockevent_devices);
                /* 主要执行tick_setup_device()创建tick device  */
            --> tick_check_new_device()
            --> clockevents_notify_released()
        创建tick设备,同时设置当前传统时钟下clock event设备的event_handler
kernel/time/tick_common.c
    tick_check_new_device()
            /* Setup the tick device */
        --> tick_setup_device()
                /* 设置其周期tick_perio;*/
            --> tick_period = NSEC_PER_SEC / HZ;
                /* 设置tick device模式为TICKDEV_MODE_PERIODIC */
            --> td->mode = TICKDEV_MODE_PERIODIC;
                /*
                 * 根据tick设备mode,调用tick_setup_periodic创建tick device
                 * 设置clock event设备的event_handler和operating state
                 */
            --> tick_setup_periodic()
                    /* 设置clock event设备arch_timer_evt的event_handler */
                --> tick_set_periodic_handler(dev, broadcast);  
                    /*
                     * linux内核启动初期,tick仍然使用传统时钟实现,此时
                     * clock event设备的event_handler为tick_handle_periodic()
                     * 后期会尝试切换到到高精度模式,然后修改clock event设备的event_handler
                     * 高精度模式下clock event设备的event_handler为hrtimer_interrupt()
                     */
                    --> dev->event_handler = tick_handle_periodic;
                    /* 设置clock event设备的operating state为periodic
                --> clockevents_switch_state(dev, CLOCK_EVT_STATE_PERIODIC)
1.2.3 传统时钟下tick处理
        每次tick便会产生一次arch_timer中断, 此时arch_timer中断处理中执行的clock event设备arch_timer_evt的event_handler。linux-2.6.16后虽然支持了高精度时钟,但是内核启动初期,tick仍采用传统时钟实现,传统时钟下,clock event设备arch_timer_evt的event_handler为tick_handle_periodic()。
        tick_handle_periodic()的代码逻辑如下:
kernel/time/tick_common.c
    /* Event handler for periodic ticks */
    tick_handle_periodic()
            /* 周期性时钟具体处理函数 */
        --> tick_periodic()
                /* Keep track of the next tick event */
            --> tick_next_period = ktime_add(tick_next_period, tick_period);
                /* jiffies计数加“1” */
            --> do_timer(1);
                /* 更新墙上时间 */
            --> update_wall_time();
                /* 主要处理函数,切换高精度时钟、scheduler_tick()执行 */
            --> update_process_times()
        update_process_times()的主要工作如下:
  1. update_process_times()首先会判断是否已经切到了高精度模式,如果是直接执行步骤3操作;
  2. 内核启动前期,必然没有切到高精度模式。这个时候每个时钟tick便会检查是否可以切换,如果可以,则使用hrtimer_switch_to_hres()中尝试切换到高精度模式;否则在__hrtimer_run_queues中执行对应的定时器超时处理。
  3. 执行scheduler_tick()。
kernel/time/timer.c
    update_process_times()
        --> run_local_timers()
                /*
                * 判断是否切换到了高精度模式,如果切换到了高精度模式,不做任何处理
                * 如果没有切到高精度模式,判断是否具备切换到高精度模式条件
                * 如果具备条件,执行hrtimer_switch_to_hres()中尝试切换到高精度模式
                * 否则执行__hrtimer_run_queues()进行定时器超时查询处理
                */
            --> hrtimer_run_queues();
        --> scheduler_tick();    /* 检查是否需要进行任务切换 */
1.2.4 切换到高精度模式:调度时钟(sched_timer)初始化及启动
        切换到高精度模式主要完成以下工作:
  1. 将clock event设备的event_handler改为hrtimer_interrupt()。高精度模式下,时钟到期后发生arch_timer中断,执行hrtimer_interrupt()完成到时时钟的处理,即执行超时处理函数。
  2. 将tick device的mode和clock eventdevice的state设置为oneshot。
  3. 初始化并启动高精度定时器sched_timer,周期为tick_period,超时处理函数为tick_sched_timer
linux内核切换到高精度模式后,tick通过高精度定时器sched_timer实现。
kernel/time/hrtimer.c
    hrtimer_run_queues(void)
            /* 如果已经是高精度模式,直接return */
        --> if (__hrtimer_hres_active(cpu_base))    return;
                /*
                 * 这里其实有个判断,如果当前满足条件,
                 * 执行hrtimer_switch_to_hres()尝试切换到高精度模式
                 */
            --> hrtimer_switch_to_hres()
                    /* 切换到高精度模式 */
                --> tick_init_highres()
                        /*
                         * 将clock event设备的event_handler改为hrtimer_interrupt;
                         * 将tick device的mode和clock eventdevice的state设置为oneshot
                         */
                    --> tick_switch_to_oneshot(hrtimer_interrupt);
                            /* 设置tick device mode为oneshot */
                        --> td->mode = TICKDEV_MODE_ONESHOT;
                            /*
                             * 设置高精度模式下clock event设备的event_handler
                             * 从之前的tick_handle_periodic切换为hrtimer_interrupt
                             */
                        --> dev->event_handler =hrtimer_interrupt;
                            /* 设置clock event设备的state为oneshot */
                        --> clockevents_switch_state(dev, CLOCK_EVT_STATE_ONESHOT);
                    /* 初始化并启动sched_timer */
                --> tick_setup_sched_timer()
                    --> hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_HARD);
                        /* sched_timer超时处理函数为tick_sched_timer */
                    --> ts->sched_timer.function = tick_sched_timer; 
                    --> hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());
                    --> 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_run_queues()进行定时器超时查询处理
                 */
            --> __hrtimer_run_queues()
1.2.5 高精度模式下,tick处理:sched_timer超时处理
        当linux内核切换到高精度模式后,时钟节拍tick通过sched_timer高精度定时器实现,当定时器sched_timer超时后触发arch_timer中断,arch_timer中断处理中执行clock event设备的event_handler,此时为hrtimer_interrupt(),hrtimer_interrupt()完成到时定时器的处理,即执行超时处理函数tick_sched_timer()。
kernel/time/tick-sched.c
    tick_sched_timer()
        /* 增加jiffies计数、更新墙上时间 */
        --> tick_sched_do_timer(ts, now); 
        --> tick_sched_handle(ts, regs);
            /*
             * 此方法上面已经介绍过了,不同的是:这边检查到已经切换到高精度模式后,
             * 不在进行其他操作,直接执行scheduler_tick(),后面有简单逻辑介绍。
             */
            --> update_process_times(user_mode(regs));
        /* 重新推迟定时器,时间为tick_period,即下一个tick */
        --> hrtimer_forward(timer, now, tick_period);
        --> return HRTIMER_RESTART;
kernel/time/time.c
    update_process_times()
        --> run_local_timers();
            /* 因为已经切换到高精度模式,所以这里不做任何操作,直接return了 */
            --> hrtimer_run_queues() 
                { 
                    if (__hrtimer_hres_active(cpu_base))  return;
                } 
        /* 检查是否需要进行任务切换 */
        --> scheduler_tick();
1.2.6 scheduler_tick
        linux内核在每个tick进行任务切换检查,检查标志TIF_NEED_RESCHED是否被设置,如被设置,则进行任务切换,详细参考linux任务调度。
kernel/sched/core.c
    scheduler_tick() { curr->sched_class->task_tick(rq, curr, 0); }
        /* 根据当前任务所属调度类执行对应task_tick方法 */
        --> task_tick_fair() / task_tick_rt() / task_tick_dl()
Logo

更多推荐