您尚未登录。

#1 2021-04-22 10:52:20 分享评论

无根浮萍
会员
注册时间: 2021-01-14
已发帖子: 60
积分: 29.5

【转】 Linux 系统Tick刨根究底

https://v2as.com/article/478a8ec2-efa3-4bc4-86a0-90cbd90ad766

1. Tick的作用
          操作系统,tick仿佛是人的脉搏,不停的向各个器官提供血液。 Tick在操作系统中,会进行调度,是分时调度最基础的组成部分。在学习Linux内核时,我只知道这个概念,却没能真正看到它是如何实现的。在经历了大约2个周的仔细阅读之后,终于能窥其全貌。

          jiffies的更新。

         kernel/time/timekeeping.c

void do_timer(unsigned long ticks)
{
        jiffies_64 += ticks;
        update_wall_time();
        calc_global_load(ticks);
}

kernel/time/tick-common.c

static void tick_periodic(int cpu)
{
        if (tick_do_timer_cpu == cpu) {
                write_seqlock(&jiffies_lock);

                /* Keep track of the next tick event */
                tick_next_period = ktime_add(tick_next_period, tick_period);


 
                do_timer(1);
                write_sequnlock(&jiffies_lock);
        }

        update_process_times(user_mode(get_irq_regs()));
        profile_tick(CPU_PROFILING);
}

/*
 * Event handler for periodic ticks
 */
void tick_handle_periodic(struct clock_event_device *dev)
{
        int cpu = smp_processor_id();
        ktime_t next;

        tick_periodic(cpu);

        if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
                return;
        /*
         * Setup the next period for devices, which do not have
         * periodic mode:
         */
        next = ktime_add(dev->next_event, tick_period);
        for (;;) {
                if (!clockevents_program_event(dev, next, false))
                        return;
                /*
                 * Have to be careful here. If we're in oneshot mode,
                 * before we call tick_periodic() in a loop, we need
                 * to be sure we're using a real hardware clocksource.
                 * Otherwise we could get trapped in an infinite
                 * loop, as the tick_periodic() increments jiffies,
                 * when then will increment time, posibly causing
                 * the loop to trigger again and again.
                 */
                if (timekeeping_valid_for_hres())
                        tick_periodic(cpu);
                next = ktime_add(next, tick_period);
        }
}

kernel/time/tick-broadcast.c

/*
 * Set the periodic handler depending on broadcast on/off
 */
void tick_set_periodic_handler(struct clock_event_device *dev, int broadcast)
{
        if (!broadcast)
                dev->event_handler = tick_handle_periodic;
        else
                dev->event_handler = tick_handle_periodic_broadcast;
}

kernel/time/tick-common.c

/*
 * Setup the device for a periodic tick
 */
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
        tick_set_periodic_handler(dev, broadcast);

        /* Broadcast setup ? */
        if (!tick_device_is_functional(dev))
                return;


 
        if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
            !tick_broadcast_oneshot_active()) {
                clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);
        } else {
                unsigned long seq;
                ktime_t next;

                do {
                        seq = read_seqbegin(&jiffies_lock);
                        next = tick_next_period;
                } while (read_seqretry(&jiffies_lock, seq));

                clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);

                for (;;) {
                        if (!clockevents_program_event(dev, next, false))
                                return;
                        next = ktime_add(next, tick_period);
                }
        }
}

/*
 * Setup the tick device
 */
static void tick_setup_device(struct tick_device *td,
                              struct clock_event_device *newdev, int cpu,
                              const struct cpumask *cpumask)
{
        ktime_t next_event;
        void (*handler)(struct clock_event_device *) = NULL;

        /*

…..

        */
        if (tick_device_uses_broadcast(newdev, cpu))
                return;

        if (td->mode == TICKDEV_MODE_PERIODIC)
                tick_setup_periodic(newdev, 0);
        else
                tick_setup_oneshot(newdev, handler, next_event);
}


 
/*
 * Check, if the new registered device should be used.
 */
void tick_check_new_device(struct clock_event_device *newdev)
{
        struct clock_event_device *curdev;
        struct tick_device *td;
        int cpu;
        unsigned long flags;

……..

        }
        clockevents_exchange_device(curdev, newdev);
        tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
        if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
                tick_oneshot_notify();

        raw_spin_unlock_irqrestore(&tick_device_lock, flags);
        return;

out_bc:
        /*
         * Can the new device be used as a broadcast device ?
         */
        tick_install_broadcast_device(newdev);
        raw_spin_unlock_irqrestore(&tick_device_lock, flags);
}

      Tick的eventhandler是在clock_event_device的注册过程中设置的。

2. clock_event_device的注册继续沿着调用关系向下寻找。

kernel/time/clockevents.c

**
 * clockevents_register_device - register a clock event device
 * @dev:        device to register
 */
void clockevents_register_device(struct clock_event_device *dev)
{
        unsigned long flags;

        BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED);
        if (!dev->cpumask) {
                WARN_ON(num_possible_cpus() > 1);
                dev->cpumask = cpumask_of(smp_processor_id());
        }

        raw_spin_lock_irqsave(&clockevents_lock, flags);

        list_add(&dev->list, &clockevent_devices);
        tick_check_new_device(dev);
        clockevents_notify_released();

        raw_spin_unlock_irqrestore(&clockevents_lock, flags);
}


 
void clockevents_config_and_register(struct clock_event_device *dev,
                                     u32 freq, unsigned long min_delta,
                                     unsigned long max_delta)
{
        dev->min_delta_ticks = min_delta;
        dev->max_delta_ticks = max_delta;
        clockevents_config(dev, freq);
        clockevents_register_device(dev);
}

还是基于我相对熟悉的Arm作为例子。
drivers/clocksource/arm_arch_timer.c

static void __cpuinit __arch_timer_setup(unsigned type,
                                       struct clock_event_device *clk)
{
        clk->features = CLOCK_EVT_FEAT_ONESHOT;

        if (type == ARCH_CP15_TIMER) {
                clk->features |= CLOCK_EVT_FEAT_C3STOP;
                clk->name = "arch_sys_timer";
                clk->rating = 450;
                clk->cpumask = cpumask_of(smp_processor_id());
                if (arch_timer_use_virtual) {
                        clk->irq = arch_timer_ppi[VIRT_PPI];
                        clk->set_mode = arch_timer_set_mode_virt;
                        clk->set_next_event = arch_timer_set_next_event_virt;
                } else {
                        clk->irq = arch_timer_ppi[PHYS_SECURE_PPI];
                        clk->set_mode = arch_timer_set_mode_phys;
                        clk->set_next_event = arch_timer_set_next_event_phys;
                }
        } else {
                clk->features |= CLOCK_EVT_FEAT_DYNIRQ;
                clk->name = "arch_mem_timer";
                clk->rating = 400;
                clk->cpumask = cpu_all_mask;
                if (arch_timer_mem_use_virtual) {
                        clk->set_mode = arch_timer_set_mode_virt_mem;
                        clk->set_next_event =
                                arch_timer_set_next_event_virt_mem;
                } else {
                        clk->set_mode = arch_timer_set_mode_phys_mem;
                        clk->set_next_event =
                                arch_timer_set_next_event_phys_mem;
                }
        }

        clk->set_mode(CLOCK_EVT_MODE_SHUTDOWN, clk);

        clockevents_config_and_register(clk, arch_timer_rate, 0xf, 0x7fffffff);
}

static int __cpuinit arch_timer_setup(struct clock_event_device *clk)
{
        __arch_timer_setup(ARCH_CP15_TIMER, clk);

……

}

static int __init arch_timer_register(void)
{
        int err;
        int ppi;

        arch_timer_evt = alloc_percpu(struct clock_event_device);
        if (!arch_timer_evt) {
                err = -ENOMEM;
                goto out;
        }

……..

        /* Immediately configure the timer on the boot CPU */
        arch_timer_setup(this_cpu_ptr(arch_timer_evt));

        return 0;

out_unreg_notify:
        unregister_cpu_notifier(&arch_timer_cpu_nb);
out_free_irq:
        if (arch_timer_use_virtual)
                free_percpu_irq(arch_timer_ppi[VIRT_PPI], arch_timer_evt);
        else {
                free_percpu_irq(arch_timer_ppi[PHYS_SECURE_PPI],
                                arch_timer_evt);
                if (arch_timer_ppi[PHYS_NONSECURE_PPI])
                        free_percpu_irq(arch_timer_ppi[PHYS_NONSECURE_PPI],
                                        arch_timer_evt);
        }

out_free:
        free_percpu(arch_timer_evt);
out:
        return err;
}


 
static void __init arch_timer_init(struct device_node *np)
{
        int i;

        if (arch_timers_present & ARCH_CP15_TIMER) {
                pr_warn("arch_timer: multiple nodes in dt, skipping\n");
                return;
        }

        arch_timers_present |= ARCH_CP15_TIMER;
        for (i = PHYS_SECURE_PPI; i < MAX_TIMER_PPI; i++)
                arch_timer_ppi[i] = irq_of_parse_and_map(np, i);
        arch_timer_detect_rate(NULL, np);

        /*
         * If HYP mode is available, we know that the physical timer
         * has been configured to be accessible from PL1. Use it, so
         * that a guest can use the virtual timer instead.
         *
         * If no interrupt provided for virtual timer, we'll have to
         * stick to the physical timer. It'd better be accessible…
         */
        if (is_hyp_mode_available() || !arch_timer_ppi[VIRT_PPI]) {
                arch_timer_use_virtual = false;

                if (!arch_timer_ppi[PHYS_SECURE_PPI] ||
                    !arch_timer_ppi[PHYS_NONSECURE_PPI]) {
                        pr_warn("arch_timer: No interrupt available, giving up\n");
                        return;
                }
        }

        arch_timer_register();
        arch_timer_common_init();
}

CLOCKSOURCE_OF_DECLARE(armv7_arch_timer, "arm,armv7-timer", arch_timer_init);
CLOCKSOURCE_OF_DECLARE(armv8_arch_timer, "arm,armv8-timer", arch_timer_init);

    arch_timer_init对整个时钟进行设置。

#define CLOCKSOURCE_OF_DECLARE(name, compat, fn)                        \
        static const struct of_device_id __clksrc_of_table_##name       \
                __used __section(__clksrc_of_table)                     \
                 = { .compatible = compat,                              \
                     .data = (fn == (clocksource_of_init_fn)NULL) ? fn : fn }

CLOCKSOURCE_OF_DECLARE生成一个表项。

3. time_init

arch/arm64/kernel/time.c

void __init time_init(void)
{
        u32 arch_timer_rate;

        of_clk_init(NULL);
        clocksource_of_init();

        tick_setup_hrtimer_broadcast();

        arch_timer_rate = arch_timer_get_rate();
        if (!arch_timer_rate)
                panic("Unable to initialise architected timer.\n");

        /* Calibrate the delay loop directly */
        lpj_fine = arch_timer_rate / HZ;
}

drivers/clocksource/clksrc-of.c

void __init clocksource_of_init(void)
{
        struct device_node *np;
        const struct of_device_id *match;
        clocksource_of_init_fn init_func;

        for_each_matching_node_and_match(np, __clksrc_of_table, &match) {
                init_func = match->data;
                init_func(np);
        }
}

4. 整个故事到了尾声, clocksource_of_init将遍历device tree所有的节点,如果找到和__clksrc_of_tablematch的, 就调用相应的函数。

device tree的一个Node:

timer {
                        compatible = "arm,armv8-timer";
                        interrupts = <0x1 0x2 0xff08 0x1 0x3 0xff08 0x1 0x4 0xff08 0x1 0x1 0xff08>;
                        clock-frequency = <0x124f800>;
                };

离线

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn