精华内容
下载资源
问答
  • run_timer_softirq
    2021-05-16 10:21:35

    因此,我的setup_timer()中的TimerExpire函数会引起巨大的恐慌(将在下面发布),而对TimerExpire()的常规函数​​调用实际上将输出我的输入。

    void TimerExpire(char* data)

    {

    printk("Timer Data: %s\n", data);

    }

    setup_timer(&my_timer, TimerExpire, (char *)args);

    printk("Made timer: %s\n", (char *)args);

    TimerExpire((char *)args);

    有人知道为什么吗?

    这是错误输出(顺便说一下,这是在gumstix verdex仿真器上,它是Linux内核):

    # Unable to handle kernel paging request at virtual address be940eb2

    pgd = c0004000

    [be940eb2] *pgd=00000000

    Internal error: Oops: 35 [#1]

    Modules linked in: mytimer ipv6 pxa2xx_cs pxa2xx_core pcmcia pcmcia_core firmware_class pxamci mmc_block mmc_core

    CPU: 0

    PC is at strnlen+0x20/0x34

    LR is at vsnprintf+0x318/0x5c8

    pc : [] lr : [] Not tainted

    sp : c01b9d88 ip : c01b9d98 fp : c01b9d94

    r10: 00000000 r9 : c01ca148 r8 : ffffffff

    r7 : c01ce468 r6 : c01c9d54 r5 : be940eb2 r4 : c01b9e94

    r3 : c01a0808 r2 : be940eb2 r1 : fffffffe r0 : be940eb2

    Flags: NzCv IRQs off FIQs on Mode SVC_32 Segment kernel

    Control: 7977

    Table: A3488000 DAC: 00000017

    Process swapper (pid: 0, stack limit = 0xc01b8258)

    Stack: (0xc01b9d88 to 0xc01ba000)

    9d80: c01b9de4 c01b9d98 c00d7d4c c00d6bd4 00000000 c01b9e4c

    9da0: 00000989 00000033 c01b9e24 00000400 c01c9d48 bf06523d 000080d5 00000400

    9dc0: bf065054 c01b9e94 c01ce468 00000000 69054114 c01b8000 c01b9dfc c01b9de8

    9de0: c00d814c c00d7a40 00000000 bf065230 c01b9e74 c01b9e00 c00381b8 c00d8140

    9e00: c01b9e24 20000193 00000001 60000113 00000000 c0276db0 00000000 00000003

    9e20: c01b9e3c c01b9e30 c003468c c0034508 c01b9e6c c01b9e40 c0033268 c0034684

    9e40: 00000989 20000193 c01b9ec4 00000100 bf065054 bf065944 c01ce468 00000000

    9e60: 69054114 c01b8000 c01b9e8c c01b9e78 c003845c c003810c bf065944 c01b9e94

    9e80: c01b9eac c01b9ea0 bf06504c c0038444 bf065230 be940eb2 c01b9ec8 60000113

    9ea0: c01b9ebc c01b9eb0 bf065064 bf065040 c01b9ef4 c01b9ec0 c003ffb8 bf065060

    9ec0: bf065960 c0040d08 c01b9ec8 c01b9ec8 00000001 c01ce264 0000000a c01e1d7c

    9ee0: a001419c a0014168 c01b9f14 c01b9ef8 c003c7c4 c003fe60 69054114 0000001a

    9f00: c01ba680 00000000 c01b9f24 c01b9f18 c003cb88 c003c770 c01b9f44 c01b9f28

    9f20: c002957c c003cb50 c00086f4 ffffffff c01b9f7c 04000000 c01b9f9c c01b9f48

    9f40: c0028830 c0029540 00000001 c01b8000 a0000013 20000013 c0029d44 c01b8000

    9f60: c00153e8 c01e1d7c a001419c 69054114 a0014168 c01b9f9c c01b9f90 c01b9f90

    9f80: c0029d8c c0029d98 20000013 ffffffff c01b9fb4 c01b9fa0 c0029b1c c0029d50

    9fa0: c01dc20c c01c88b0 c01b9fc4 c01b9fb8 c0028138 c0029af0 c01b9ff4 c01b9fc8

    9fc0: c0008adc c0028120 c00083e4 00000000 00000000 c00153e8 00000000 00007975

    9fe0: c01c8964 c01be264 00000000 c01b9ff8 a0008030 c00088bc 00000000 00000000

    Backtrace:

    [] (strnlen+0x0/0x34) from [] (vsnprintf+0x318/0x5c8)

    [] (vsnprintf+0x0/0x5c8) from [] (vscnprintf+0x18/0x24)

    [] (vscnprintf+0x0/0x24) from [] (vprintk+0xb8/0x334)

    r4 = BF065230

    [] (vprintk+0x0/0x334) from [] (printk+0x28/0x30)

    [] (printk+0x0/0x30) from [] (PrintMessage+0x18/0x20 [mytimer])

    r3 = 60000113 r2 = C01B9EC8 r1 = BE940EB2 r0 = BF065230

    [] (PrintMessage+0x0/0x20 [mytimer]) from [] (TimerExpire+0x10/0x14 [mytimer])

    [] (TimerExpire+0x0/0x14 [mytimer]) from [] (run_timer_softirq+0x164/0x1e8)

    [] (run_timer_softirq+0x0/0x1e8) from [] (__do_softirq+0x60/0xd4)

    [] (__do_softirq+0x0/0xd4) from [] (irq_exit+0x44/0x4c)

    r6 = 00000000 r5 = C01BA680 r4 = 0000001A

    [] (irq_exit+0x0/0x4c) from [] (asm_do_IRQ+0x48/0x60)

    [] (asm_do_IRQ+0x0/0x60) from [] (__irq_svc+0x30/0x80)

    r6 = 04000000 r5 = C01B9F7C r4 = FFFFFFFF

    [] (default_idle+0x0/0x5c) from [] (cpu_idle+0x38/0x54)

    [] (cpu_idle+0x0/0x54) from [] (rest_init+0x24/0x2c)

    r5 = C01C88B0 r4 = C01DC20C

    [] (rest_init+0x0/0x2c) from [] (start_kernel+0x22c/0x284)

    [] (start_kernel+0x0/0x284) from [] (0xa0008030)

    Code: ea000000 e2800001 e2511001 3a000002 (e5d03000)

    Kernel panic - not syncing: Aiee, killing interrupt handler!

    更多相关内容
  • 浅析linux内核中timer定时器的生成和sofirq软中断调用流程mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(&base->lock);禁止cpu中断,所以我们的timer回调处理函数handler...

    浅析linux内核中timer定时器的生成和sofirq软中断调用流程

    mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(&base->lock);禁止cpu中断,所以我们的timer回调处理函数handler工作在irq关闭的环境中,所以需要作很多考虑,比如在handler中尽量不要执行会引起pending的函数调用,比如kmalloc之类可能引起pending的操作,否则会使kernel永远停在我们的handler中不能返回,这样kernel将因为我们ko设计上的失败而死机[luther.gliethttp]!

    我们可以使用如下几行语句,向我们的ko驱动添加一个timer定时器,来处理时间事件:

    struct __wlanwlan_check_tx_flow_timer

    {

    struct timer_list timer;

    int timer_freq;

    } wlan_check_tx_flow_timer = {

    .timer_freq = 8*1000,

    };

    static void wlan_check_tx_flow_timer_handler(unsigned long data)

    {

    ...

    //重新启动timer定时器

    mod_timer(&wlan_check_tx_flow_timer.timer, jiffies + msecs_to_jiffies(wlan_check_tx_flow_timer.timer_freq));

    ...

    }

    //设置定时器

    setup_timer(&wlan_check_tx_flow_timer.timer, wlan_check_tx_flow_timer_handler, (unsigned long)&wlan_check_tx_flow_timer);

    //添加定时器

    mod_timer(&wlan_check_tx_flow_timer.timer, jiffies + msecs_to_jiffies(wlan_check_tx_flow_timer.timer_freq));

    那么这个wlan_check_tx_flow_timer_handler处理函数在什么时候被调用的呢?那么我们追入内核中,看看kernel对定时器的具体管理.

    首先kernel在启动的最前面注册TIMER_SOFTIRQ的处理函数[luther.gliethttp],

    start_kernel

    =>init_timers

    =>open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);

    那么由谁来调用raise_softirq(TIMER_SOFTIRQ);触发TIMER_SOFTIRQ软中断呢,这就和平台相关了,对于pxa935处理器来说[luther.gliethttp],

    MACHINE_START(LUTHER, "luther")

    .phys_io = 0x40000000,

    .boot_params = 0xa0000100,

    .io_pg_offst = (io_p2v(0x40000000) >> 18) & 0xfffc,

    .map_io = pxa_map_io,

    .init_irq = pxa3xx_init_irq,

    .timer = &pxa_timer,

    .init_machine = luther_init,

    MACHINE_END

    =>pxa_timer_init//平台对应的定时器初始化

    ==>pxa_timer_irq.dev_id = &ckevt_32ktimer;

    ==>setup_irq(IRQ_OST_4_11, &pxa_timer_irq); //32768的rtc

    ==>clockevents_register_device(&ckevt_32ktimer);

    pxa_timer_interrupt中断处理函数

    =>c->event_handler(c);也就是tick_handle_periodic系统时钟函数

    =>tick_handle_periodic

    =>update_process_times

    =>run_local_timers

    =>raise_softirq(TIMER_SOFTIRQ);

    这里仅仅是触发了TIMER_SOFTIRQ软中断,那么在什么地方处理我们mod_timer添加的timer定时器处理函数wlan_check_tx_flow_timer_handler呢[luther.gliethttp]?

    __irq_svc://内核中发生的中断__irq_usr://用户空间时发生的中断=>asm_do_IRQ

    =>irq_exit

    =>do_softirq

    =>__do_softirq

    =>调用上面注册的run_timer_softirq软中断处理函数

    =>run_timer_softirq

    =>__run_timers

    static inline void __run_timers(struct tvec_base *base)

    {

    struct timer_list *timer;

    spin_lock_irq(&base->lock);//禁止中断    while (time_after_eq(jiffies, base->timer_jiffies)) {

    ...

    if (时间到了) {

    ...

    fn = timer->function;

    data = timer->data;

    fn(data);//这就是我们上面添加的static void wlan_check_tx_flow_timer_handler(unsigned long data);定时器处理函数了.

    ...

    }

    ...

    }

    set_running_timer(base, NULL);

    spin_unlock_irq(&base->lock);//打开中断

    }

    //================

    include/asm/hardirq.h

    typedef struct {

    unsigned int __softirq_pending;

    unsigned int local_timer_irqs;

    } ____cacheline_aligned irq_cpustat_t;

    //================

    kernel/softirq.c|45| irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

    #ifndef __ARCH_IRQ_STAT

    irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;//在这里定义irq_stat存储空间

    EXPORT_SYMBOL(irq_stat);

    #endif

    //================

    include/linux/irq_cpustat.h

    #ifndef __ARCH_IRQ_STAT

    //引用的就是上面的irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

    extern irq_cpustat_t irq_stat[];        /* defined in asm/hardirq.h */

    #define __IRQ_STAT(cpu, member)    (irq_stat[cpu].member)

    #endif

    //================

    arch/arm/kernel/entry-armv.S|331| .word    irq_stat

    #ifdef CONFIG_PREEMPT

    svc_preempt:

    teq    r8, #0                @ was preempt count = 0

    ldreq    r6, .LCirq_stat //操作

    movne    pc, lr                @ no

    ldr    r0, [r6, #4]            @ local_irq_count

    ldr    r1, [r6, #8]            @ local_bh_count

    adds    r0, r0, r1

    movne    pc, lr

    mov    r7, #0                @ preempt_schedule_irq

    str    r7, [tsk, #TI_PREEMPT]        @ expects preempt_count == 0

    1:    bl    preempt_schedule_irq        @ irq en/disable is done inside

    ldr    r0, [tsk, #TI_FLAGS]        @ get new tasks TI_FLAGS

    tst    r0, #_TIF_NEED_RESCHED

    beq    preempt_return            @ go again

    b    1b

    #endif

    .LCirq_stat:

    .word    irq_stat //引用irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;地址

    #endif

    /* arch independent irq_stat fields */

    #define local_softirq_pending() \

    __IRQ_STAT(smp_processor_id(), __softirq_pending)

    #define __ARCH_IRQ_EXIT_IRQS_DISABLED    1

    #ifdef __ARCH_IRQ_EXIT_IRQS_DISABLED

    # define invoke_softirq()    __do_softirq() //是这个

    #else

    # define invoke_softirq()    do_softirq()

    #endif

    #ifndef __ARCH_SET_SOFTIRQ_PENDING

    #define set_softirq_pending(x) (local_softirq_pending() = (x))

    #define or_softirq_pending(x) (local_softirq_pending() |= (x))

    #endif

    #define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)

    inline void raise_softirq_irqoff(unsigned int nr)

    {

    __raise_softirq_irqoff(nr);

    if (!in_interrupt())

    wakeup_softirqd();

    }

    void raise_softirq(unsigned int nr)

    {

    unsigned long flags;

    local_irq_save(flags);

    raise_softirq_irqoff(nr);

    local_irq_restore(flags);

    }

    =>s3c2410_timer_interrupt

    =>timer_tick

    =>pxa_timer_init

    ==>pxa_timer_irq.dev_id = &ckevt_32ktimer;

    ==>setup_irq(IRQ_OST_4_11, &pxa_timer_irq); //32768的rtc

    ==>clockevents_register_device(&ckevt_32ktimer);

    =>clockevents_register_device

    =>clockevents_do_notify

    =>raw_notifier_call_chain(&clockevents_chain, reason, dev);

    =>__raw_notifier_call_chain

    =>notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);

    =>nb->notifier_call(nb, val, v);就是tick_notify

    start_kernel

    =>tick_init

    static struct notifier_block tick_notifier = {

    .notifier_call = tick_notify,

    };

    void __init tick_init(void)

    {

    clockevents_register_notifier(&tick_notifier);

    }

    clockevents_register_notifier

    =>raw_notifier_chain_register(&clockevents_chain, nb);

    =>notifier_chain_register将tick_notifier添加到clockevents_chain这个单向链表中[luther.gliethttp]

    static int tick_notify(struct notifier_block *nb, unsigned long reason,

    void *dev)

    {

    switch (reason) {

    case CLOCK_EVT_NOTIFY_ADD:

    return tick_check_new_device(dev);

    ...

    return NOTIFY_OK;

    }

    =>tick_notify

    =>tick_check_new_device

    =>tick_setup_device(td, newdev, cpu, cpumask);

    static void tick_setup_device(struct tick_device *td,

    struct clock_event_device *newdev, int cpu,

    cpumask_t cpumask)

    {

    ktime_t next_event;

    void (*handler)(struct clock_event_device *) = NULL;

    /*

    * First device setup ?

    */

    if (!td->evtdev) {

    /*

    * If no cpu took the do_timer update, assign it to

    * this cpu:

    */

    if (tick_do_timer_cpu == -1) {

    tick_do_timer_cpu = cpu;

    tick_next_period = ktime_get();

    tick_period = ktime_set(0, NSEC_PER_SEC / HZ);

    }

    /*

    * Startup in periodic mode first.

    */

    td->mode = TICKDEV_MODE_PERIODIC;//设置第1个tick设备为TICKDEV_MODE_PERIODIC模式    } else {

    handler = td->evtdev->event_handler;

    next_event = td->evtdev->next_event;

    }

    td->evtdev = newdev;

    ...

    if (td->mode == TICKDEV_MODE_PERIODIC)

    tick_setup_periodic(newdev, 0);

    else

    tick_setup_oneshot(newdev, handler, next_event);

    }

    void tick_setup_periodic(struct clock_event_device *dev, int broadcast)

    {

    tick_set_periodic_handler(dev, broadcast);//设置event_handler处理函数为dev->event_handler = tick_handle_periodic;

    /* Broadcast setup ? */

    if (!tick_device_is_functional(dev))

    return;

    if (dev->features & CLOCK_EVT_FEAT_PERIODIC) {

    clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);

    } else {

    unsigned long seq;

    ktime_t next;

    do {

    seq = read_seqbegin(&xtime_lock);

    next = tick_next_period;

    } while (read_seqretry(&xtime_lock, seq));

    clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);

    for (;;) {

    if (!clockevents_program_event(dev, next, ktime_get()))

    return;

    next = ktime_add(next, tick_period);

    }

    }

    }

    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;

    }

    =>pxa_timer_interrupt

    {

    ...

    if (OSSR & OST_C4) {

    OIER &= ~OST_C4;

    OSSR = OST_C4;

    if (timer32k_enabled)

    c->event_handler(c);//调用tick_handle_periodic处理函数,作为    }

    ...

    }

    void tick_handle_periodic(struct clock_event_device *dev)

    {

    int cpu = smp_processor_id();

    ktime_t next;

    tick_periodic(cpu);//调用do_timer(1);将jiffies_64加1

    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, ktime_get()))

    return;

    tick_periodic(cpu);

    next = ktime_add(next, tick_period);

    }

    }

    static void tick_periodic(int cpu)

    {

    if (tick_do_timer_cpu == cpu) {

    write_seqlock(&xtime_lock);

    /* Keep track of the next tick event */

    tick_next_period = ktime_add(tick_next_period, tick_period);

    do_timer(1);

    write_sequnlock(&xtime_lock);

    }

    update_process_times(user_mode(get_irq_regs()));

    profile_tick(CPU_PROFILING);

    }

    arch/arm/kernel/time.c|332| update_process_times(user_mode(get_irq_regs()));

    =>update_process_times

    =>run_local_timers

    =>raise_softirq(TIMER_SOFTIRQ);//触发软中断,当irq_exit时调用__do_softirq来处理

    =>run_timer_softirq

    =>__run_timers

    =>

    fn = timer->function;//执行

    data = timer->data;

    fn(data);

    //================

    include/asm/arch-pxa/entry-macro.S|22| .macro    get_irqnr_and_base, irqnr, irqstat, base, tmp

    //pxa获取irq中断号函数

    //================

    arch/arm/kernel/entry-armv.S|37| bne    asm_do_IRQ

    .macro    irq_handler

    get_irqnr_preamble r5, lr

    1:    get_irqnr_and_base r0, r6, r5, lr //获取irq中断号,存储到r0寄存器中,作为参数传递给asm_do_IRQ    movne    r1, sp

    @

    @ routine called with r0 = irq number, r1 = struct pt_regs *

    @

    adrne    lr, 1b

    bne    asm_do_IRQ

    ...

    //================    .align    5

    __irq_svc://内核中发生的中断    svc_entry

    ...

    irq_handler

    ...

    //================    .align    5

    __irq_usr://用户空间时发生的中断    usr_entry

    ...

    irq_handler

    ...

    //================    .macro    vector_stub, name, mode, correction=0

    .align    5

    vector_\name:

    .if \correction

    sub    lr, lr, #\correction

    .endif

    @

    @ Save r0, lr_ (parent PC) and spsr_

    @ (parent CPSR)

    @

    stmia    sp, {r0, lr}        @ save r0, lr

    mrs    lr, spsr

    str    lr, [sp, #8]        @ save spsr

    @

    @ Prepare for SVC32 mode. IRQs remain disabled.

    @

    mrs    r0, cpsr

    eor    r0, r0, #(\mode ^ SVC_MODE)

    msr    spsr_cxsf, r0

    @

    @ the branch table must immediately follow this code

    @

    and    lr, lr, #0x0f //lr存储了spsr,所以一共有16种cpu模式    mov    r0, sp //传参    ldr    lr, [pc, lr, lsl #2]//取出相应模式下的处理函数指针,比如__irq_usr或者__irq_svc    movs    pc, lr            @ branch to handler in SVC mode

    .endm

    //================    .globl    __stubs_start

    __stubs_start:

    /*

    * Interrupt dispatcher

    */

    vector_stub    irq, IRQ_MODE, 4

    .long    __irq_usr            @ 0 (USR_26 / USR_32)

    .long    __irq_invalid        @ 1 (FIQ_26 / FIQ_32)

    .long    __irq_invalid        @ 2 (IRQ_26 / IRQ_32)

    .long    __irq_svc            @ 3 (SVC_26 / SVC_32)

    .long    __irq_invalid            @ 4

    .long    __irq_invalid            @ 5

    .long    __irq_invalid            @ 6

    .long    __irq_invalid            @ 7

    .long    __irq_invalid            @ 8

    .long    __irq_invalid            @ 9

    .long    __irq_invalid            @ a

    .long    __irq_invalid            @ b

    .long    __irq_invalid            @ c

    .long    __irq_invalid            @ d

    .long    __irq_invalid            @ e

    .long    __irq_invalid            @ f

    //================    .globl    __vectors_start

    __vectors_start:

    swi    SYS_ERROR0

    b    vector_und + stubs_offset

    ldr    pc, .LCvswi + stubs_offset

    b    vector_pabt + stubs_offset

    b    vector_dabt + stubs_offset

    b    vector_addrexcptn + stubs_offset

    b    vector_irq + stubs_offset

    b    vector_fiq + stubs_offset

    //================asm_do_IRQ(unsigned int irq, struct pt_regs *regs)

    =>desc_handle_irq(irq, desc);//static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)

    {

    desc->handle_irq(irq, desc);//调用中断号irq对应的handler回调处理函数[luther.gliethttp]}

    __irq_svc://内核中发生的中断__irq_usr://用户空间时发生的中断=>asm_do_IRQ

    =>irq_exit

    =>do_softirq

    =>__do_softirq

    =>

    {

    ...

    h = softirq_vec;//执行软中断函数

    do {

    if (pending & 1) {

    h->action(h);

    //如果32768的时间到达,那asm_do_IRQ中将触发raise_softirq(TIMER_SOFTIRQ);//在这里将执行管理系统tick的run_timer_softirq软中断[luther.gliethttp]            rcu_bh_qsctr_inc(cpu);

    }

    h++;

    pending >>= 1;

    } while (pending);

    ...

    }

    start_kernel

    =>init_timers

    =>open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);

    void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)

    {

    softirq_vec[nr].data = data;

    softirq_vec[nr].action = action;

    }

    static void run_timer_softirq(struct softirq_action *h)

    {

    struct tvec_base *base = __get_cpu_var(tvec_bases);//获得time时间根

    hrtimer_run_pending();

    if (time_after_eq(jiffies, base->timer_jiffies))

    __run_timers(base);

    }

    //执行软中断=>run_timer_softirq

    =>__run_timers

    =>

    fn = timer->function;

    data = timer->data;

    fn(data);

    static inline void __run_timers(struct tvec_base *base)

    {

    ...

    spin_lock_irq(&base->lock);//禁止中断    ...

    fn = timer->function;

    data = timer->data;

    fn(data);

    ...

    set_running_timer(base, NULL);

    spin_unlock_irq(&base->lock);//打开中断}

    mod_timer

    =>__mod_timer

    int __mod_timer(struct timer_list *timer, unsigned long expires)

    {

    struct tvec_base *base, *new_base;

    unsigned long flags;

    int ret = 0;

    timer_stats_timer_set_start_info(timer);

    BUG_ON(!timer->function);

    base = lock_timer_base(timer, &flags);

    if (timer_pending(timer)) {

    detach_timer(timer, 0);

    ret = 1;

    }

    new_base = __get_cpu_var(tvec_bases);//获得time时间根

    if (base != new_base) {

    /*

    * We are trying to schedule the timer on the local CPU.

    * However we can't change timer's base while it is running,

    * otherwise del_timer_sync() can't detect that the timer's

    * handler yet has not finished. This also guarantees that

    * the timer is serialized wrt itself.

    */

    if (likely(base->running_timer != timer)) {

    /* See the comment in lock_timer_base() */

    timer_set_base(timer, NULL);

    spin_unlock(&base->lock);

    base = new_base;

    spin_lock(&base->lock);

    timer_set_base(timer, base);

    }

    }

    timer->expires = expires;

    internal_add_timer(base, timer);

    //添加到链表上,这样当timer超时到达时,run_timer_softirq=>__run_timers软中断中将会回调该处理函数[luther.gliethttp].    spin_unlock_irqrestore(&base->lock, flags);

    return ret;

    }

    展开全文
  • */ static __latent_entropy void run_timer_softirq(struct softirq_action *h) { struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]); //获取本cpu的BASE_STD timer_base __run_timers(base); -- ...

    持续更新

    基于Linux Kernel version 4.14.193.

    kernel/time/timer.c


    2020.11.02

    1. 初始化流程

    1.1 调用流程:

    start_kernel
        init_timers {
            init_timer_cpus
            open_softirq
        }

    1.2 timer的初始化函数:

    void __init init_timers(void)
    {
        init_timer_cpus();  -- (1) //对每个cpu进行timer初始化
        open_softirq(TIMER_SOFTIRQ, run_timer_softirq); -- (2) //注册softirq回调函数
    }

    (1)

    timer_bases是per cpu变量,在NO_HZ状态下,每个cpu存在两个timer_base,一个BASE_STD,一个BASE_DEF。

    BASE_STD为标准的timer,BASE_DEF为可延迟的timer。

    static void __init init_timer_cpus(void)
    {
        int cpu;
    
        for_each_possible_cpu(cpu) //遍历cpu
            init_timer_cpu(cpu);
    }
    
    static void __init init_timer_cpu(int cpu)
    {
        struct timer_base *base;
        int i;
    
        for (i = 0; i < NR_BASES; i++) { //NR_BASES 是timer bases的个数,在NO_HZ时为2 一个是normal timer base,一个是deferred timer base。
            base = per_cpu_ptr(&timer_bases[i], cpu); //取出本cpu的timer_base
            base->cpu = cpu; //初始化 timer_base cpu信息
            raw_spin_lock_init(&base->lock); //初始化锁
            base->clk = jiffies; //初始化起始时间
        }
    }

    (2)

    低精度timer使用的处理机制为softirq,softirq可以被硬中断打断,但不能被调度出cpu或被抢占。

    softirq定义在kernel/softirq.c中,后续再详细讨论。

    void __init init_timers(void)
    {
        init_timer_cpus();
        open_softirq(TIMER_SOFTIRQ, run_timer_softirq); -- (3) //注册软中断回调函数
    }
    

    (3)

    注册timer的回调函数run_timer_softirq

    /*
     * This function runs timers and the timer-tq in bottom half context.
     */
    static __latent_entropy void run_timer_softirq(struct softirq_action *h)
    {
        struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]); //获取本cpu的BASE_STD timer_base
    
        __run_timers(base); -- (4) //处理BASE_STD的过期timer
        if (IS_ENABLED(CONFIG_NO_HZ_COMMON))
            __run_timers(this_cpu_ptr(&timer_bases[BASE_DEF])); //处理BASE_DEF的过期timer
    }
    

    2. 过期timer处理流程

    (4) __run_timers

    在__run_timers中会新建一个heads数据,它是一个局部变量,在collect_expired_timer函数中收集过期timer,将过期的level所有timer迁移至heads上。再针对每一级过期Level处理所有过期timer。

    /**
     * __run_timers - run all expired timers (if any) on this CPU.
     * @base: the timer vector to be processed.
     */
    static inline void __run_timers(struct timer_base *base)
    {
        struct hlist_head heads[LVL_DEPTH]; // 创建一个临时数组heads,它的深度为 LVL_DEPTH,与timer_base->vectors格式相同
        int levels;
    
        if (!time_after_eq(jiffies, base->clk)) // 若当前时间在base->clk前,直接返回
            return;
    
        raw_spin_lock_irq(&base->lock); // 拿锁 timer base锁
    
        /*
         * timer_base::must_forward_clk must be cleared before running
         * timers so that any timer functions that call mod_timer() will
         * not try to forward the base. Idle tracking / clock forwarding
         * logic is only used with BASE_STD timers.
         *
         * The must_forward_clk flag is cleared unconditionally also for
         * the deferrable base. The deferrable base is not affected by idle
         * tracking and never forwarded, so clearing the flag is a NOOP.
         *
         * The fact that the deferrable base is never forwarded can cause
         * large variations in granularity for deferrable timers, but they
         * can be deferred for long periods due to idle anyway.
         */
        base->must_forward_clk = false; //必须在运行前配置为false
    
        while (time_after_eq(jiffies, base->clk)) { //若当前时间在base->clk之后
    
            levels = collect_expired_timers(base, heads); -- (5) //计算出过期timer的level
            base->clk++; //将base->clk++,代表timer_base clk前的timer已处理
    
            while (levels--) //对每一级level进行过期timer处理
                expire_timers(base, heads + levels); -- (8)
        }
        base->running_timer = NULL; //全部timer处理完成后,将running_timer配置为NULL
        raw_spin_unlock_irq(&base->lock); // 放锁 timer base锁
    }
    

    (5) collect_expired_timers

    static int collect_expired_timers(struct timer_base *base,
                      struct hlist_head *heads)
    {
        unsigned long now = READ_ONCE(jiffies); //获取系统当前时间
    
        /*
         * NOHZ optimization. After a long idle sleep we need to forward the
         * base to current jiffies. Avoid a loop by searching the bitfield for
         * the next expiring timer.
         */
        if ((long)(now - base->clk) > 2) { //正常是每个tick都会检查,出现idle时会>2
            unsigned long next = __next_timer_interrupt(base); -- (6) /获取下一次timer的时间
    
            /*
             * If the next timer is ahead of time forward to current
             * jiffies, otherwise forward to the next expiry time:
             */
            if (time_after(next, now)) { //若下一次timer的时间在当前系统时间之后,那么认为没有过期timer
                /* The call site will increment clock! */
                base->clk = now - 1;
                return 0;
            }
            base->clk = next; //否则,将下一次timer的时间赋给base->clk,查找过期timer
        }
        return __collect_expired_timers(base, heads); -- (7)
    }

    (6) __next_timer_interrupt

    找到下一个过期timer,返回时间。

    (7) __collect_expired_timers

    clk的低3bit是什么 含义,为什么去判断这个? if (clk & LVL_CLK_MASK)

    static int __collect_expired_timers(struct timer_base *base,
                        struct hlist_head *heads)
    {
        unsigned long clk = base->clk;
        struct hlist_head *vec;
        int i, levels = 0;
        unsigned int idx;
    
        for (i = 0; i < LVL_DEPTH; i++) { //遍历每一个LEVEL
            idx = (clk & LVL_MASK) + i * LVL_SIZE; //取clk的后6bit,加上offset,计算出base->clk的idx
    
            if (__test_and_clear_bit(idx, base->pending_map)) { //若idx对应的timer为pending状态
                vec = base->vectors + idx; //获取到timer_base的vectors对应的hlist
                hlist_move_list(vec, heads++); //从timer_base上移除,添加至heads上,并将heads++
                levels++; //将levels++ 代表收集到的timer个数
            }
            /* Is it time to look at the next level? */
            if (clk & LVL_CLK_MASK) //若clk的后3bit不是000,则break,跳出查找??? 为什么做这个判断?
                break;
            /* Shift clock for the next level granularity */
            clk >>= LVL_CLK_SHIFT; //clk右移3bit,下一个bucket
        }
        return levels;
    }

    (8) expire_timers

    传入参数为timer_base,用于赋值running_timer。与heads对应的具体level的head,它是将要处理的timer所在链表。在while循环中会对head上所有的timer全部处理。

    static void expire_timers(struct timer_base *base, struct hlist_head *head)
    {
        while (!hlist_empty(head)) {
            struct timer_list *timer;
            void (*fn)(unsigned long);
            unsigned long data;
    
            timer = hlist_entry(head->first, struct timer_list, entry); //根据timer_list中的entry找到对应timer_list
    
            base->running_timer = timer; //赋值timer_base,为正在处理的timer
            detach_timer(timer, true); //将timer_list从hlist_head中移除,此时该timer变成非pending状态,会将timer_list->entry的pprev & next配置为无效值。
    
            fn = timer->function;
            data = timer->data;
    
            if (timer->flags & TIMER_IRQSAFE) {
                raw_spin_unlock(&base->lock);
                call_timer_fn(timer, fn, data); //处理timer
                raw_spin_lock(&base->lock);
            } else {
                raw_spin_unlock_irq(&base->lock);
                call_timer_fn(timer, fn, data);
                raw_spin_lock_irq(&base->lock);
            }
        }
    }

    3. 删除一个timer

    3.1 最基本函数detach_timer

    操作timer节点删除最终都会调用到detach_timer函数,它的作用是将timer从链表中删除。

    static inline void detach_timer(struct timer_list *timer, bool clear_pending)
    {
        struct hlist_node *entry = &timer->entry; //获取将要删除timer对应的entry
    
        debug_deactivate(timer);
    
        __hlist_del(entry); //从hlist上删除该entry
        if (clear_pending) //若是需要清除pending状态
            entry->pprev = NULL; //将pprev赋值为NULL,代表该timer为非pending状态
        entry->next = LIST_POISON2; //将entry->next赋值为无效值
    }

    3.2 调用关系

     

     

     

     

     

     

     

     

     


     

    展开全文
  • 定时器 timer rtoax 2021年3月 在原文基础上,增加5.10.13内核源码相关内容。 1. Timers This is fourth part of the chapter which describes timers and time management related stuff in the Linux kernel ...
    Linux内核深入理解定时器和时间管理
    定时器 timer


    rtoax
    2021年3月

    原文基础上,增加5.10.13内核源码相关内容。

    1. Timers

    This is fourth part of the chapter which describes timers and time management related stuff in the Linux kernel and in the previous part we knew about the tick broadcast framework and NO_HZ mode in the Linux kernel. We will continue to dive into the time management related stuff in the Linux kernel in this part and will be acquainted with yet another concept in the Linux kernel - timers. Before we will look at timers in the Linux kernel, we have to learn some theory about this concept. Note that we will consider software timers in this part.

    The Linux kernel provides a software timer concept to allow to kernel functions could be invoked at future moment. Timers are widely used in the Linux kernel. For example, look in the net/netfilter/ipset/ip_set_list_set.c source code file. This source code file provides implementation of the framework for the managing of groups of IP addresses.

    We can find the list_set structure that contains gc filed in this source code file:

    struct list_set {
    	...
    	struct timer_list gc;
    	...
    };
    

    Not that the gc field has timer_list type. This structure defined in the include/linux/timer.h header file and main point of this structure is to store dynamic timers in the Linux kernel. Actually, the Linux kernel provides two types of timers called dynamic timers and interval timers. First type of timers is used by the kernel, and the second can be used by user mode. The timer_list structure contains actual dynamic timers. The list_set contains gc timer in our example represents timer for garbage collection. This timer will be initialized in the list_set_gc_init function:

    static void
    list_set_gc_init(struct ip_set *set, void (*gc)(unsigned long ul_set))
    {
    	struct list_set *map = set->data;
    	...
    	...
    	...
    	map->gc.function = gc;
    	map->gc.expires = jiffies + IPSET_GC_PERIOD(set->timeout) * HZ;
    	...
    	...
    	...
    }
    

    A function that is pointed by the gc pointer, will be called after timeout which is equal to the map->gc.expires.

    Ok, we will not dive into this example with the netfilter, because this chapter is not about network related stuff. But we saw that timers are widely used in the Linux kernel and learned that they represent concept which allows to functions to be called in future.

    Now let’s continue to research source code of Linux kernel which is related to the timers and time management stuff as we did it in all previous chapters.

    2. Introduction to dynamic timers

    timer_list是基于时间轮(timer wheel)算法的。

    这里有一张表:

    
     * HZ 1000 steps
     * Level Offset  Granularity            Range
     *  0      0         1 ms                0 ms -         63 ms
     *  1     64         8 ms               64 ms -        511 ms
     *  2    128        64 ms              512 ms -       4095 ms (512ms - ~4s)
     *  3    192       512 ms             4096 ms -      32767 ms (~4s - ~32s)
     *  4    256      4096 ms (~4s)      32768 ms -     262143 ms (~32s - ~4m)
     *  5    320     32768 ms (~32s)    262144 ms -    2097151 ms (~4m - ~34m)
     *  6    384    262144 ms (~4m)    2097152 ms -   16777215 ms (~34m - ~4h)
     *  7    448   2097152 ms (~34m)  16777216 ms -  134217727 ms (~4h - ~1d)
     *  8    512  16777216 ms (~4h)  134217728 ms - 1073741822 ms (~1d - ~12d)
     *
     * HZ  300
     * Level Offset  Granularity            Range
     *  0	   0         3 ms                0 ms -        210 ms
     *  1	  64        26 ms              213 ms -       1703 ms (213ms - ~1s)
     *  2	 128       213 ms             1706 ms -      13650 ms (~1s - ~13s)
     *  3	 192      1706 ms (~1s)      13653 ms -     109223 ms (~13s - ~1m)
     *  4	 256     13653 ms (~13s)    109226 ms -     873810 ms (~1m - ~14m)
     *  5	 320    109226 ms (~1m)     873813 ms -    6990503 ms (~14m - ~1h)
     *  6	 384    873813 ms (~14m)   6990506 ms -   55924050 ms (~1h - ~15h)
     *  7	 448   6990506 ms (~1h)   55924053 ms -  447392423 ms (~15h - ~5d)
     *  8    512  55924053 ms (~15h) 447392426 ms - 3579139406 ms (~5d - ~41d)
     *
     * HZ  250
     * Level Offset  Granularity            Range
     *  0	   0         4 ms                0 ms -        255 ms
     *  1	  64        32 ms              256 ms -       2047 ms (256ms - ~2s)
     *  2	 128       256 ms             2048 ms -      16383 ms (~2s - ~16s)
     *  3	 192      2048 ms (~2s)      16384 ms -     131071 ms (~16s - ~2m)
     *  4	 256     16384 ms (~16s)    131072 ms -    1048575 ms (~2m - ~17m)
     *  5	 320    131072 ms (~2m)    1048576 ms -    8388607 ms (~17m - ~2h)
     *  6	 384   1048576 ms (~17m)   8388608 ms -   67108863 ms (~2h - ~18h)
     *  7	 448   8388608 ms (~2h)   67108864 ms -  536870911 ms (~18h - ~6d)
     *  8    512  67108864 ms (~18h) 536870912 ms - 4294967288 ms (~6d - ~49d)
     *
     * HZ  100
     * Level Offset  Granularity            Range
     *  0	   0         10 ms               0 ms -        630 ms
     *  1	  64         80 ms             640 ms -       5110 ms (640ms - ~5s)
     *  2	 128        640 ms            5120 ms -      40950 ms (~5s - ~40s)
     *  3	 192       5120 ms (~5s)     40960 ms -     327670 ms (~40s - ~5m)
     *  4	 256      40960 ms (~40s)   327680 ms -    2621430 ms (~5m - ~43m)
     *  5	 320     327680 ms (~5m)   2621440 ms -   20971510 ms (~43m - ~5h)
     *  6	 384    2621440 ms (~43m) 20971520 ms -  167772150 ms (~5h - ~1d)
     *  7	 448   20971520 ms (~5h) 167772160 ms - 1342177270 ms (~1d - ~15d)
    

    As I already wrote, we knew about the tick broadcast framework and NO_HZ mode in the previous part. They will be initialized in the init/main.c source code file by the call of the tick_init function. If we will look at this source code file, we will see that the next time management related function is:

    init_timers();
    

    This function defined in the kernel/time/timer.c source code file and contains calls of four functions:

    void __init init_timers(void)
    {
    	init_timer_cpus();
    	init_timer_stats();
    	timer_register_cpu_notifier();
    	open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
    }
    

    Let’s look on implementation of each function. The first function is init_timer_cpus defined in the same source code file and just calls the init_timer_cpu function for each possible processor in the system:

    static void __init init_timer_cpus(void)
    {
    	int cpu;
    
    	for_each_possible_cpu(cpu)
    		init_timer_cpu(cpu);
    }
    

    If you do not know or do not remember what is it a possible cpu, you can read the special part of this book which describes cpumask concept in the Linux kernel. In short words, a possible processor is a processor which can be plugged in anytime during the life of the system.

    The init_timer_cpu function does main work for us, namely it executes initialization of the tvec_base structure for each processor(5.10.13中这个结构叫做timer_bases). This structure defined in the kernel/time/timer.c source code file and stores data related to a dynamic timer for a certain processor. Let’s look on the definition of this structure:

    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;
    	bool migration_enabled;
    	bool nohz_active;
    	struct tvec_root tv1;
    	struct tvec tv2;
    	struct tvec tv3;
    	struct tvec tv4;
    	struct tvec tv5;
    } ____cacheline_aligned;
    

    The thec_base structure contains following fields: The lock for tvec_base protection, the next running_timer field points to the currently running timer for the certain processor, the timer_jiffies fields represents the earliest expiration time (it will be used by the Linux kernel to find already expired timers). The next field - next_timer contains the next pending timer for a next timer interrupt in a case when a processor goes to sleep and the NO_HZ mode is enabled in the Linux kernel. The active_timers field provides accounting of non-deferrable timers or in other words all timers that will not be stopped during a processor will go to sleep. The all_timers field tracks total number of timers or active_timers + deferrable timers. The cpu field represents number of a processor which owns timers. The migration_enabled and nohz_active fields are represent opportunity of timers migration to another processor and status of the NO_HZ mode respectively.

    The last five fields of the tvec_base structure represent lists of dynamic timers. The first tv1 field has:

    #define TVR_SIZE (1 << TVR_BITS)
    #define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)
    
    ...
    ...
    ...
    
    struct tvec_root {
    	struct hlist_head vec[TVR_SIZE];
    };
    

    type. Note that the value of the TVR_SIZE depends on the CONFIG_BASE_SMALL kernel configuration option:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qG2y3oWf-1617165688967)(images/base_small.png)]

    that reduces size of the kernel data structures if disabled. The v1 is array that may contain 64 or 256 elements where an each element represents a dynamic timer that will decay within the next 255 system timer interrupts. Next three fields: tv2, tv3 and tv4 are lists with dynamic timers too, but they store dynamic timers which will decay the next 2^14 - 1, 2^20 - 1 and 2^26 respectively. The last tv5 field represents list which stores dynamic timers with a large expiring period.

    So, now we saw the tvec_base structure and description of its fields and we can look on the implementation of the init_timer_cpu function. As I already wrote, this function defined in the kernel/time/timer.c source code file and executes initialization of the tvec_bases:

    static void __init init_timer_cpu(int cpu)
    {
    	struct tvec_base *base = per_cpu_ptr(&tvec_bases, cpu);
    
    	base->cpu = cpu;
    	spin_lock_init(&base->lock);
    
    	base->timer_jiffies = jiffies;
    	base->next_timer = base->timer_jiffies;
    }
    

    The tvec_bases represents per-cpu variable which represents main data structure for a dynamic timer for a given processor. This per-cpu variable defined in the same source code file:

    static DEFINE_PER_CPU(struct tvec_base, tvec_bases);
    

    First of all we’re getting the address of the tvec_bases for the given processor to base variable and as we got it, we are starting to initialize some of the tvec_base fields in the init_timer_cpu function. After initialization of the per-cpu dynamic timers with the jiffies and the number of a possible processor, we need to initialize a tstats_lookup_lock spinlock in the init_timer_stats function:

    void __init init_timer_stats(void)
    {
    	int cpu;
    
    	for_each_possible_cpu(cpu)
    		raw_spin_lock_init(&per_cpu(tstats_lookup_lock, cpu));
    }
    

    The tstats_lookcup_lock variable represents per-cpu raw spinlock:

    static DEFINE_PER_CPU(raw_spinlock_t, tstats_lookup_lock);
    

    which will be used for protection of operation with statistics of timers that can be accessed through the procfs:

    static int __init init_tstats_procfs(void)
    {
    	struct proc_dir_entry *pe;
    
    	pe = proc_create("timer_stats", 0644, NULL, &tstats_fops);
    	if (!pe)
    		return -ENOMEM;
    	return 0;
    }
    

    For example(5.10.13中没有这个数据):

    $ cat /proc/timer_stats
    Timerstats sample period: 3.888770 s
      12,     0 swapper          hrtimer_stop_sched_tick (hrtimer_sched_tick)
      15,     1 swapper          hcd_submit_urb (rh_timer_func)
       4,   959 kedac            schedule_timeout (process_timeout)
       1,     0 swapper          page_writeback_init (wb_timer_fn)
      28,     0 swapper          hrtimer_stop_sched_tick (hrtimer_sched_tick)
      22,  2948 IRQ 4            tty_flip_buffer_push (delayed_work_timer_fn)
      ...
      ...
      ...
    

    The next step after initialization of the tstats_lookup_lock spinlock is the call of the timer_register_cpu_notifier function. This function depends on the CONFIG_HOTPLUG_CPU kernel configuration option which enables support for hotplug processors in the Linux kernel.

    When a processor will be logically offlined, a notification will be sent to the Linux kernel with the CPU_DEAD or the CPU_DEAD_FROZEN event by the call of the cpu_notifier macro:

    #ifdef CONFIG_HOTPLUG_CPU
    ...
    ...
    static inline void timer_register_cpu_notifier(void)
    {
    	cpu_notifier(timer_cpu_notify, 0);
    }
    ...
    ...
    #else
    ...
    ...
    static inline void timer_register_cpu_notifier(void) { }
    ...
    ...
    #endif /* CONFIG_HOTPLUG_CPU */
    

    In this case the timer_cpu_notify will be called which checks an event type and will call the migrate_timers function:

    static int timer_cpu_notify(struct notifier_block *self,
    	                        unsigned long action, void *hcpu)
    {
    	switch (action) {
    	case CPU_DEAD:
    	case CPU_DEAD_FROZEN:
    		migrate_timers((long)hcpu);
    		break;
    	default:
    		break;
    	}
    
    	return NOTIFY_OK;
    }
    

    This chapter will not describe hotplug related events in the Linux kernel source code, but if you are interesting in such things, you can find implementation of the migrate_timers function in the kernel/time/timer.c source code file.

    The last step in the init_timers function is the call of the:

    open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
    

    function. The open_softirq function may be already familiar to you if you have read the ninth part about the interrupts and interrupt handling in the Linux kernel. In short words, the open_softirq function defined in the kernel/softirq.c source code file and executes initialization of the deferred interrupt handler.

    In our case the deferred function is the run_timer_softirq function that is will be called after a hardware interrupt in the do_IRQ function which defined in the arch/x86/kernel/irq.c source code file. The main point of this function is to handle a software dynamic timer. The Linux kernel does not do this thing during the hardware timer interrupt handling because this is time consuming operation.

    Let’s look on the implementation of the run_timer_softirq function:

    static void run_timer_softirq(struct softirq_action *h)
    {
    	struct tvec_base *base = this_cpu_ptr(&tvec_bases);
    
    	if (time_after_eq(jiffies, base->timer_jiffies))
    		__run_timers(base);
    }
    

    At the beginning of the run_timer_softirq function we get a dynamic timer for a current processor and compares the current value of the jiffies with the value of the timer_jiffies for the current structure by the call of the time_after_eq macro which is defined in the include/linux/jiffies.h header file:

    #define time_after_eq(a,b)          \
        (typecheck(unsigned long, a) && \
         typecheck(unsigned long, b) && \
        ((long)((a) - (b)) >= 0))
    

    Reclaim that the timer_jiffies field of the tvec_base structure represents the relative time when functions delayed by the given timer will be executed. So we compare these two values and if the current time represented by the jiffies is greater than base->timer_jiffies, we call the __run_timers function that defined in the same source code file. Let’s look on the implementation of this function.

    As I just wrote, the __run_timers function runs all expired timers for a given processor. This function starts from the acquiring of the tvec_base's lock to protect the tvec_base structure

    static inline void __run_timers(struct tvec_base *base)
    {
    	struct timer_list *timer;
    
    	spin_lock_irq(&base->lock);
    	...
    	...
    	...
    	spin_unlock_irq(&base->lock);
    }
    

    After this it starts the loop while the timer_jiffies will not be greater than the jiffies:

    while (time_after_eq(jiffies, base->timer_jiffies)) {
    	...
    	...
    	...
    }
    

    We can find many different manipulations in the our loop, but the main point is to find expired timers and call delayed functions. First of all we need to calculate the index of the base->tv1 list that stores the next timer to be handled with the following expression:

    index = base->timer_jiffies & TVR_MASK;
    

    where the TVR_MASK is a mask for the getting of the tvec_root->vec elements. As we got the index with the next timer which must be handled we check its value. If the index is zero, we go through all lists in our cascade table tv2, tv3 and etc., and rehashing it with the call of the cascade function:

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

    After this we increase the value of the base->timer_jiffies:

    ++base->timer_jiffies;
    

    In the last step we are executing a corresponding function for each timer from the list in a following loop:

    hlist_move_list(base->tv1.vec + index, head);
    
    while (!hlist_empty(head)) {
    	...
    	...
    	...
    	timer = hlist_entry(head->first, struct timer_list, entry);
    	fn = timer->function;
    	data = timer->data;
    
    	spin_unlock(&base->lock);
    	call_timer_fn(timer, fn, data);
    	spin_lock(&base->lock);
    
    	...
    	...
    	...
    }
    

    where the call_timer_fn just call the given function:

    static void call_timer_fn(struct timer_list *timer, void (*fn)(unsigned long),
    	                      unsigned long data)
    {
    	...
    	...
    	...
    	fn(data);
    	...
    	...
    	...
    }
    

    That’s all. The Linux kernel has infrastructure for dynamic timers from this moment. We will not dive into this interesting theme. As I already wrote the timers is a widely used concept in the Linux kernel and nor one part, nor two parts will not cover understanding of such things how it implemented and how it works. But now we know about this concept, why does the Linux kernel needs in it and some data structures around it.

    Now let’s look usage of dynamic timers in the Linux kernel.

    3. Usage of dynamic timers

    As you already can noted, if the Linux kernel provides a concept, it also provides API for managing of this concept and the dynamic timers concept is not exception here. To use a timer in the Linux kernel code, we must define a variable with a timer_list type. We can initialize our timer_list structure in two ways. The first is to use the init_timer macro that defined in the include/linux/timer.h header file:

    #define init_timer(timer)    \
    	__init_timer((timer), 0)
    
    #define __init_timer(_timer, _flags)   \
             init_timer_key((_timer), (_flags), NULL, NULL)
    

    where the init_timer_key function just calls the:

    do_init_timer(timer, flags, name, key);
    

    function which fields the given timer with default values. The second way is to use the:

    #define TIMER_INITIALIZER(_function, _expires, _data)		\
    	__TIMER_INITIALIZER((_function), (_expires), (_data), 0)
    

    macro which will initialize the given timer_list structure too.

    After a dynamic timer is initialized we can start this timer with the call of the:

    void add_timer(struct timer_list * timer);
    

    function and stop it with the:

    int del_timer(struct timer_list * timer);
    

    function.

    That’s all.

    4. Conclusion

    This is the end of the fourth part of the chapter that describes timers and timer management related stuff in the Linux kernel. In the previous part we got acquainted with the two new concepts: the tick broadcast framework and the NO_HZ mode. In this part we continued to dive into time management related stuff and got acquainted with the new concept - dynamic timer or software timer. We didn’t saw implementation of a dynamic timers management code in details in this part but saw data structures and API around this concept.

    In the next part we will continue to dive into timer management related things in the Linux kernel and will see new concept for us - timers.

    If you have questions or suggestions, feel free to ping me in twitter 0xAX, drop me email or just create issue.

    Please note that English is not my first language and I am really sorry for any inconvenience. If you found any mistakes please send me PR to linux-insides.

    5. Links

    展开全文
  • open_softirq(TIMER_SOFTIRQ, run_timer_softirq);// 时钟中断   init_timer(); //初始 timer_list 的结构的一些变量 add_timer() //将timer 加入内核timer列表中,等待处理   以下为例子源码,仅供参考   ...
  • del_timer_sync 的kernel code 有个反例如注释 为了解决这种死锁问题,采用 try_to_del_timer_sync /** * del_timer_sync - deactivate a timer and wait for the handler to finish. * @timer: the timer to be ...
  • from [] (run_timer_softirq+0x228/0x310) [] (run_timer_softirq+0x228/0x310) from [] (__do_softirq+0xc0/0x190) [] (__do_softirq+0xc0/0x190) from [] (irq_exit+0x48/0x50) [] (irq_exit+0x48/0x50) from [] ...
  • 逻辑上的定时器是正常的中断,即发生硬件中断,会去查看有没有软中断要处理,定时器通过软中断来实现,属于TIMER_SOFTIRQ软中断 void __init init_timers(void) { init_timer_cpus(); init_timer_stats();
  • arch_timer驱动分析

    千次阅读 2019-11-06 16:23:05
    arch_timer的初始化 static void __init arch_timer_common_init(void) { arch_timer_banner(arch_timers_present);//打印计时器的信息 arch_counter_register(arch_timers_present);//注册计数器 arch_timer_...
  • 最近一个项目,做机器重启测试时,连续测试一段...大概是在定时器timer回调处理函数里面。 代码如下: static struct timer_list set_sp_work_timer; static int set_sp_gpio_val; static void timer_func(unsig...
  • linux时间子系统

    2018-03-21 20:27:53
    对linux时间子系统研究,整理出的文档 可以作为手册进行查阅
  • 3.1 唤醒中断 ( __raise_softirq_irqoff ) 4触发软中断 5 收发包软中断执行 6并行优化 6.1RSS/RPS/RFS/XPS 6.1.1 RSS (Receive Side Scaling) (接收侧的缩放) 6.1.2 RPS Receive Packet Steering (接收端包的...
  • Linux 内核时钟之经典timer处理

    千次阅读 2017-03-24 18:06:05
    /*  * This function runs timers and the timer-tq in bottom half context. ...static __latent_entropy void run_timer_softirq(struct softirq_action *h) {  struct timer_base *base = this_cpu_ptr(&ti
  • Linux 之软中断softirq

    千次阅读 2018-10-22 16:50:13
    softirq驱动开发人员一般都不会用到,到内核代码中会用到softirq机制,如在timer定时器中有用到softirq机制。...void open_softirq(int nr, void (*action) (struct softirq_action *)); /** * @nr: 软中...
  • init_timers();

    千次阅读 2011-11-27 15:07:19
    void __init init_timers(void) ... int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE, (void *)(long)smp_processor_id()); //因为是初始化阶段,所以得到的CPU为启动CPU init_
  • 可以从这个方向开始思考,我们先来看下这个函数的实现: static void run_timer_softirq(struct softirq_action *h) { struct tvec_base *base = __this_cpu_read(tvec_bases); hrtimer_run_pending(); __...
  • 目前,trace-irqoff 已开源,如感兴趣详见 Open Source Repo( https://github.com/bytedance/trace-irqoff/tree/master ) 。在工作中,我们经常遇到业务的进程网络延迟高。基于此前分析同类问题的丰富...softirq 关...
  • 8.3.2 add_timer

    千次阅读 2012-11-14 20:05:07
    当程序定义了一个定时器对象,并且通过init_timer函数及相应代码对该定时器对象中的expires、data和function等成员初始化之后,程序需要调用add_timer将该定时器对象加入到系统中,这样定时器才会在expires表示的...
  • 内核kernel panic错误定位

    千次阅读 2012-10-18 19:42:12
    [ 4000.945000] LR is at run_timer_softirq+0x17c/0x1f4 [ 4000.945000] pc : [] lr : [] psr: 20000013 [ 4000.945000] sp : c3b53dd8 ip : c3b53df0 fp : c3b53dec [ 4000.945000] r10: c0485c30 r9 : ...
  • 遍历完成后,会再次获取__softirq_pending,如果在遍历softirq_vec期间又产生了softirq,__do_softirq会跳转到restart再次遍历处理softirq。 最多反复处理MAX_SOFTIRQ_RESTART次。 如果还有softirq的pending,则...
  • 时钟中断导致的内核模块死锁

    千次阅读 2013-04-02 17:41:48
    run_timer_softirq+0x197/0x340 [] ? tick_sched_timer+0x0/0xc0 [] ? lapic_next_event+0x1d/0x30 [] ? __do_softirq+0xc1/0x1e0 [] ? hrtimer_interrupt+0x140/0x250 [] ? call_softirq+0x1c/0x30 [] ? do_...
  • linux的动态定时器--时间轮

    千次阅读 2012-03-29 00:33:27
    open_softirq(TIMER_SOFTIRQ, run_timer_softirq); } 在timer_cpu_notify(&timers_nb,(unsigned long)CPU_UP_PREPARE, (void*)(long)smp_processor_id()); 中执行 init_timers_cpu(cpu) //初始化本cpu中的timers ...
  • MSM8940-PMI8950 MTP (DT) [ 423.400164] task: ffffffc04eae4980 ti: ffffffc0b28bc000 task.ti: ffffffc0b28bc000 [ 423.400182] PC is at run_timer_softirq+0x4ac/0x5ec [ 423.400192] LR is at run_timer_...
  • __run_timers() -- 处理全部超时定时器 ...run_timer_softirq() --> __run_timers() /usr/src/linux-2.6.19/kernel/timer.c static inline void __run_timers(tvec_base_t *base) { structtimer_list *...
  • linux之softirq

    千次阅读 2017-01-24 18:16:29
    2)在非中断上下文中,通过调用raise_softirq_irqoff,置位irq_stat[cpu].__softirq_pending中的相应软中断位,并唤醒软中断守护进程,通过软中断守护进程实现软中断的处理; 3)在__do_softirq中,当该函数执行完时...
  • 网卡驱动的书写格式很简单 1.申请一个网卡设备结构体 ...2.设置这个结构体,硬件相关初始化 ... /* 1.... /* 2.... /* 3.... /* 1.... /* 2.... /* 3....[] (sch_direct_xmit) from [] (__qdisc_run+0x130/0x4e4) ...
  • static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;   /* PLEASE, avoid to allocate new softirqs, if you need not _really_ high   frequency threaded job scheduling. For...
  • 中断学习之timer_interrupt x86实现

    千次阅读 2018-07-14 07:54:57
    irqreturn_t timer_interrupt(int irq, void *dev_id) =>do_timer_interrupt_hook(); =>global_clock_event->event_handler(global_clock_event);//dev->event_handler = tick_handle...
  • linux内核之timer定时器

    2020-07-23 19:44:41
    TIMER_SOFTIRQ的执行函数是__run_timers,它实现了本节讨论的逻辑,取出tv1中到期的定时器,执行定时器的回调函数,由此可见,低分辨率定时器的回调函数是执行在软件中断上下文中的,这点在写定时器的回调函数时需要...
  • 硬件的中断处理函数处于中断上半部分,在CPU关中断的状态下执行,中断线程、软中断(softirq)及小任务(tasklet)属于中断的下半部分(bottom half),在CPU开中断的状态下执行。小任务基于软中断实现,实质是对软...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,629
精华内容 1,051
关键字:

run_timer_softirq