linux 2.6.23时钟中断与调度分析

Threadx 系统定时器线程_tx_timer_thread_entry “System Timer Thread” 系统定时器线程用于判断定时器超时,并调用定时器超时处理函数。 _tx_timer_initialize 先来看看定时器线程创建初始化。_tx_timer_initialize初始化系统一系列与时间,定时器相关全局变量,链表,线程等。 VOID _tx_timer_initialize(VOID) { REG_1 UINT ... 阅读详情
一:前言
时钟是整个操作系统的脉搏,它为进程的时间片调度,定时事件提供了依据.另外,用户空间的很多操作都依赖于时钟,例如select.poll,make. 操作系统管理的时间为分两种,一种称为当前时间,也即我们日常生活所用的时间.这个时间一般保存在CMOS中.主板中有特定的芯片为其提供计时依据.另外一种时间称为相对时间.例如系统运行时间.显然对计算机而然,相对时间比当前时间更为重要.

二:与时钟有关的硬件处理.
1):实时时钟(RTC)
该时钟独立于CPU和其它芯片.即使PC断电,该时钟还是继续运行.该计时由一块单独的芯片处理,并把时钟值存放CMOS.该时间可参在IRQ8上周期性的产生时间信号.频率在2Hz ~ 8192Hz之间.但在linux中,只是用RTC来获取当前时间.

2):时间戳计时器(TSC)
CPU附带了一个64位的时间戳寄存器,当时钟信号到来的时候.该寄存器内容自动加1

3):可编程中断定时器(PIC)
该设备可以周期性的发送一个时间中断信号.发送中断信号的间隔可以对其进行编程控制.在linux系统中,该中断时间间隔由HZ表示.这个时间间隔也被称为一个节拍(tick).
在 ./include/asm-i386/param.h 定义
  10#ifndef HZ
  11#define HZ 100
  12#endif
  
4):CPU本地定时器
在处理器的本地APIC还提供了另外的一定定时设备.CPU本地定时器也可以单次或者周期性的产生中断信号.与上次描述的PIC相比.它有以下几点的区别:
APIC本地计时器是32位.而PIC是16位.由此APIC本地计时器可以提供更低频率的中断信号
本地APIC只把中断信号发送给本地CPU.而PIC发送的中断信号任何CPU都可以处理
APIC定时器是基于总线时钟信号的.而PIC有自己的内部时钟振荡器

5):高精度计时器(HPET)
在linux2.6中增加了对HPET的支持.HPET是一种由intel开发的新型定时芯片.该设备有一组寄时器,每个寄时器对应有自己的时钟信号,时钟信号到来的时候就会自动加1.
实际上,在intel多理器系统与单处理器系统还有所不同:
在单处理系统中.所有计时活动过由PIC产生的时钟中断信号触发的
在多处理系统中,所有普通活动是由PIC产生的中断触发.所有具体的CPU活动,都由本地APIC触发的.

6)内核在2.6引入hrtimer,增强了clock event模型。 在当前内核里,有3种中断源将自己注册为一个clock_event_device结构:

struct clock_event_device {
    const char        *name;
    unsigned int        features;
    unsigned long        max_delta_ns;
    unsigned long        min_delta_ns;
    unsigned long        mult;
    int            shift;
    int            rating;
    int            irq;
    cpumask_t        cpumask;
    int            (*set_next_event)(unsigned long evt,
                         struct clock_event_device *);
    void            (*set_mode)(enum clock_event_mode mode,
                     struct clock_event_device *);
    void            (*event_handler)(struct clock_event_device *);
    void            (*broadcast)(cpumask_t mask);
    struct list_head    list;
    enum clock_event_mode    mode;
    ktime_t            next_event;
};

最重要的是set_next_event(), event_handler(). 前者是设置下一个clock事件的触发条件, 一般就是往clock device里重设一下定时器. 后者是event handler, 事件处理函数. 该处理函数会在时钟中断ISR里被调用. 如果这个clock用来做为ticker时钟, 那么handler的执行和之前kernel的时钟中断ISR基本相同, 类似timer_tick(). 事件处理函数可以在运行时动态替换, 这就给kernel一个改变整个时钟中断处理方式的机会, 也就给highres tick及dynamic tick一个动态挂载的机会.

        
        1) LAPIC Timer (每个CPU一个lapic_events)
           arch/x86/kernel/apic_32.c,         setup_APIC_timer():
               
                clockevents_register_device(levt);
          
           嗯,lapic_clockevent是每个lapic_events的默认值,每个CPU都在setup_APIC_Timer中用它对lapic_events
           进行初始化。

        2) HPET
           arch/x86/kernel/hpet.c,         hpet_legacy_clockevent_register():

                   clockevents_register_device(&hpet_clockevent);

        3) PIT
           arch/x86/kernel/i8253.c,         setup_pit_timer():
                  
                clockevents_register_device(&pit_clockevent);

    同时,系统中有一个唯一的global_clock_event指针,从代码上来看,它或者被赋值为&pit_clockevent,或者
    被赋值为hpet_clockevent。 我的理解是,只要CONFIG_HPET_TIMER=y,那么就是后者。 而且,LAPIC Timer只
    能是一种Local Interrupt Source,因此不可能用来给global_clock_event赋值。


7)

和硬件计时器(相关的数据结构主要有两个:

  • struct clocksource :对硬件设备的抽象,描述时钟源信息
  • struct clock_event_device :时钟的事件信息,包括当硬件时钟中断发生时要执行那些操作(实际上保存了相应函数的指针)。本文将该结构称作为“时钟事件设备”。

上述两个结构内核源代码中有较详细的注解,分别位于文件 clocksource.h 和 clockchips.h 中。需要特别注意的是结构 clock_event_device 的成员 event_handler ,它指定了当硬件时钟中断发生时,内核应该执行那些操作,也就是真正的时钟中断处理函数。

Linux 内核维护了两个链表,分别存储了系统中所有时钟源的信息和时钟事件设备的信息。这两个链表的表头在内核中分别是 clocksource_list 和 clockevent_devices 。图2-1显示了这两个链表。


图2-1 时钟源链表和时钟事件链表
时钟源链表和时钟事件链表 
    

三:时钟中断相关代码分析
time_init()是时钟初始化函数,他由asmlinkage void __init start_kernel()调用.具体代码如下:
//时钟中断初始化

/*
 * This is called directly from init code; we must delay timer setup in the
 * HPET case as we can't make the decision to turn on HPET this early in the
 * boot process.
 *
 * The chosen time_init function will usually be hpet_time_init, above, but
 * in the case of virtual hardware, an alternative function may be substituted.
 */
void __init time_init(void)
{
        tsc_init();
        late_time_init = choose_time_init();
}  
      
    

    内核中检查0号中断的中断源为HPET还是PIT的代码流程是:
   
    start_kernel() > time_init() > choose_time_init() == hpet_time_init:

                void __init hpet_time_init(void)
                {
                        if (!hpet_enable())
                                setup_pit_timer();
                        //注册时钟中断处理函数
                        time_init_hook();
                }

     注意,如果HPET失败,则使用PIT。 做完这个选择之后进行0号中断的相关设置。

     -> HPET的情况:
             hpet_enable() > hpet_legacy_clockevent_register() :
                    
                clockevents_register_device(&hpet_clockevent);
                global_clock_event = &hpet_clockevent;

     -> PIT的情况:
             setup_pit_timer() :
               
                clockevents_register_device(&pit_clockevent);
                global_clock_event = &pit_clockevent;

     -> 设置完中断源后:
             time_init_hook():

                /**
                 * static struct irqaction irq0  = {
                 *         .handler = timer_interrupt,
                 *         .flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_IRQPOLL,
                 *         .mask = CPU_MASK_NONE,
                 *         .name = "timer"
                 * };
                 */
               
                irq0.mask = cpumask_of_cpu(0);
                setup_irq(0, &irq0);
       
        可见, 不管是其中断源是HPET还是PIT, 0号中断只能由CPU0来处理,其处理例程为timer_interrupt。

注:在start_kernel()>timekeeping_init()已队系统时间进行了初始化,而在2.6以前版本中初始化时间是在time_init()函数中初始化.

接着上章,转入time_init_hook():
void __init time_init_hook(void)
{
     //注册中断处理函数
     setup_irq(0, &irq0);
}
Irq0定义如下:
static struct irqaction irq0  = { timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL};

对应的中断处理函数为:timer_interrupt():

/*
 * This is the same as the above, except we _also_ save the current
 * Time Stamp Counter value at the time of the timer interrupt, so that
 * we later on can estimate the time of day more exactly.
 */
irqreturn_t timer_interrupt(int irq, void *dev_id)
{
#ifdef CONFIG_X86_IO_APIC
        if (timer_ack) {
                /*
                 * Subtle, when I/O APICs are used we have to ack timer IRQ
                 * manually to reset the IRR bit for do_slow_gettimeoffset().
                 * This will also deassert NMI lines for the watchdog if run
                 * on an 82489DX-based system.
                 */
                spin_lock(&i8259A_lock);
                outb(0x0c, PIC_MASTER_OCW3);
                /* Ack the IRQ; AEOI will end it automatically. */
                inb(PIC_MASTER_POLL);
                spin_unlock(&i8259A_lock);
        }
#endif

        do_timer_interrupt_hook();

        if (MCA_bus) {
                /* The PS/2 uses level-triggered interrupts.  You can't
                turn them off, nor would you want to (any attempt to
                enable edge-triggered interrupts usually gets intercepted by a
                special hardware circuit).  Hence we have to acknowledge
                the timer interrupt.  Through some incredibly stupid
                design idea, the reset for IRQ 0 is done by setting the
                high bit of the PPI port B (0x61).  Note that some PS/2s,
                notably the 55SX, work fine if this is removed.  */

                u8 irq_v = inb_p( 0x61 );       /* read the current state */
                outb_p( irq_v|0x80, 0x61 );     /* reset the IRQ */
        }

        return IRQ_HANDLED;
}


核心处理函数为 do_timer_interrupt_hook():
 static inline void do_timer_interrupt_hook(void)                                                               
{
        global_clock_event->event_handler(global_clock_event);
}
而global_clock_event在前面提过了,中断处理函数初始化通过如下调用:
static inline void tick_set_periodic_handler(struct clock_event_device *dev,    
                                             int broadcast)
{
        dev->event_handler = tick_handle_periodic;
}


    
初始化dev->event_handler函数的过程:

         clockevents_register_device() >
                    clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev) > 
                             raw_notifier_call_chain(&clockevents_chain, reason, dev) > 
                                         __raw_notifier_call_chain(nh, val, v, -1, NULL) > 
                                            notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls) :
                    
                                               ...
                                            ret = nb->notifier_call(nb, val, v);
 /* 注意这个参数v,指向clockevents_register_device
  * 的参数clock_event_device  *global_clock_event;
  * 而参数nb,就是&clockevents_chain
  */
    
    
    然而,这个notifier_call函数指针赋值在start_kernel() > tick_init() :
                
            clockevents_register_notifier(&tick_notifier);
    
    而tick_notifier的定义:
                
            static struct notifier_block tick_notifier = {
                    .notifier_call = tick_notify,
            };

还没有完呢,
                   tick_notify>
                           tick_check_new_device()>
                                       tick_setup-device()>
                                                   tick_setup_periodic()>
                                                           tick_set_periodic_handler()

这个dev->event_handler()处理函数终于初始化了

直接转到tick_handle_periodic():

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, ktime_get()))
                        return;
                tick_periodic(cpu);
                next = ktime_add(next, tick_period);
        }
}
其中tick_periodic调用就是以前的一系列更新操作,包括更新进程时间片等等.
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);
}


我们忽略选择编译部份,转到do_timer()
void do_timer(unsigned long ticks)                                              
{
    // 更新jiffies计数.jiffies_64与jiffies在链接的时候,实际是指向同一个区域
        jiffies_64 += ticks;
        ////更新当前时间.xtime的更新
        update_times(ticks);
} 

Update_process_times()代码如下:
void update_process_times(int user_tick)
{
        struct task_struct *p = current;
        int cpu = smp_processor_id();
    
    //这里判断时钟中断发生用户空间,还是发生在内核模式,然后计数值加1
        /* Note: this timer irq context must be accounted for as well. */
        if (user_tick)
                account_user_time(p, jiffies_to_cputime(1));
        else
                account_system_time(p, HARDIRQ_OFFSET, jiffies_to_cputime(1));
        //激活时间软中断
        run_local_timers();
        if (rcu_pending(cpu))
                rcu_check_callbacks(cpu, user_tick);
        //减少时间片。
        scheduler_tick();
        run_posix_cpu_timers(p);
}  

run_local_timers()
void run_local_timers(void)
{
     raise_softirq(TIMER_SOFTIRQ);
}
而该中断的处理函数__run_timers():

static inline void __run_timers(tvec_base_t *base)
{
        struct timer_list *timer;

        spin_lock_irq(&base->lock);
        /*这里进入定时器处理循环,利用系统全局jiffies与定时器基准jiffies进行对比,如果前者大,则表明某些定时器进行处理了,否则表示所有的 定时器都没有超时.因为CPU可能关闭中断,引起时钟中断信号丢失.可能jiffies要大base->timer_jiffies
        while (time_after_eq(jiffies, base->timer_jiffies)) {
            //定义并初始化一个链表
                struct list_head work_list;
                struct list_head *head = &work_list;
                int index = base->timer_jiffies & TVR_MASK;

                /*
                 * Cascade timers:
                 */
                 
                 //当index == 0时,说明已经循环了一个周期
                 //则将tv2填充tv1.如果tv2为空,则用tv3填充tv2.依次类推...... 
                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));
                        
                //更新base->timer_jiffies
                ++base->timer_jiffies;
                
                //将base->tv1.vec项移至work_list.并将base->tv1.vec置空
                list_replace_init(base->tv1.vec + index, &work_list);
                
                /*如果当前找到的时间数组对应的列表不为空,则表明该列表上串连的所有定时器都已经超时,循环调用每个定时器的处理
                函数,并将其从列表中删除,直到列表为空为止。*/
                while (!list_empty(head)) {
                        void (*fn)(unsigned long);
                        unsigned long data;
            //遍历链表中的每一项.运行它所对应的函数,并将定时器从链表上脱落
            
                        timer = list_first_entry(head, struct timer_list,entry);
                        fn = timer->function;
                        data = timer->data;

                        timer_stats_account_timer(timer);

                        set_running_timer(base, timer);
                        detach_timer(timer, 1);
                        spin_unlock_irq(&base->lock);
                        {
                                int preempt_count = preempt_count();
                                fn(data);
                                if (preempt_count != preempt_count()) {
                                        printk(KERN_WARNING "huh, entered %p "
                                               "with preempt_count %08x, exited"
                                               " with %08x?\n",
                                               fn, preempt_count,
                                               preempt_count());
                                        BUG();
                                }
                        }
                        spin_lock_irq(&base->lock);
                }
        }
        set_running_timer(base, NULL);
        spin_unlock_irq(&base->lock);
}


硬件定时器计完一个jiffies之后,会引起硬件中断,在硬件中断服务程序中会触发软中断,在定时器软中断服务程序中会调用 __run_timers()完成定时器多级hash table的处理,并且处理定时时间到的所有timer。__run_timers算法实现描述如下:

  1、根据当前jiffes和base->timer_jiffies循环判断多级hash table扫描条件,如果满足条件,那么继续(2),否则退出循环。

  2、通过base->timer_jiffies计算得到V1 table中需要处理的索引项。并且将索引高层hash table中的具体项,将该项中的timer分散到低层table中去。

  3、增加base->timer_jiffies值,提取出V1中索引得到的定时器链表。

  4、如果该定时器链表不为空,那么依次处理链表中的定时器,处理过程为调用定时器的处理函数timer->function。


下面对scheduler_tick()函数做解析吧.
void scheduler_tick(void)
{
        int cpu = smp_processor_id();
        struct rq *rq = cpu_rq(cpu);
        struct task_struct *curr = rq->curr;
        u64 next_tick = rq->tick_timestamp + TICK_NSEC;

        spin_lock(&rq->lock);
        __update_rq_clock(rq);
        /*
         * Let rq->clock advance by at least TICK_NSEC:
         */
        if (unlikely(rq->clock < next_tick))
                rq->clock = next_tick;
        rq->tick_timestamp = rq->clock;
        update_cpu_load(rq);
        if (curr != rq->idle) /* FIXME: needed? */
                curr->sched_class->task_tick(rq, curr);
        spin_unlock(&rq->lock);

#ifdef CONFIG_SMP
        rq->idle_at_tick = idle_cpu(cpu);
        trigger_load_balance(rq, cpu);
#endif
}

scheduler_tick()函数负责减少运行进程的时间片计数值并且在需要时设置need_resched标志.该函数还负责平衡每个处理器的运行队列.


我们在do_timer()还漏掉了一个函数:
static inline void update_times(unsigned long ticks)
{
       //更新xtime
        update_wall_time();
        //统计TASK_RUNNING TASK_UNINTERRUPTIBLE进程数量
        calc_load(ticks);                                                       
}

软件时钟处理

实现软件时钟原理也比较简单:每一次硬件时钟中断到达时,内核更新的 jiffies ,然后将其和软件时钟的到期时间进行比较。如果 jiffies 等于或者大于软件时钟的到期时间,内核就执行软件时钟指定的函数。

接下来的几节会详细介绍 Linux2.6.23 是怎么实现软件时钟的。

  • struct timer_list :软件时钟,记录了软件时钟的到期时间以及到期后要执行的操作。

  • struct tvec_base :用于组织、管理软件时钟的结构。在 SMP 系统中,每个 CPU 有一个。

 struct timer_list 主要成员
域名类型描述
entrystruct list_head所在的链表
expiresunsigned long到期时间,以 tick 为单位
functionvoid (*)(unsigned long)回调函数,到期后执行的操作
dataunsigned long回调函数的参数
basestruct tvec_base *记录该软件时钟所在的 struct tvec_base 变量


注:一个 tick 表示的时间长度为两次硬件时钟中断发生时的时间间隔



struct tvec_base 类型的成员
域名类型描述
lockspinlock_t用于同步操作
running_timerstruct timer_list *正在处理的软件时钟
timer_jiffiesunsigned long当前正在处理的软件时钟到期时间
tv1struct tvec_root保存了到期时间从 timer_jiffies 到 timer_jiffies + 对象2之间(包括边缘值)的所有软件时钟
tv2struct tvec保存了到期时间从 timer_jiffies + 对象3到 timer_jiffies +对象4之间(包括边缘值)的 所有软件时钟
tv3struct tvec保存了到期时间从 timer_jiffies +对象5到 timer_jiffies +对象6之间(包括边缘值)的所有软件时钟
tv4struct tvec保存了到期时间从 timer_jiffies +对象7到 timer_jiffies +对象8之间(包括边缘值)的所有软件时钟
tv5struct tvec保存了到期时间从 timer_jiffies +对象9到 timer_jiffies +对象10之间(包括边缘值)的所有软件时钟


其中 tv1 的类型为 struct tvec_root ,tv 2~ tv 5的类型为 struct tvec .

struct tvec {
struct list_head vec[TVN_SIZE];
};

struct tvec_root {
struct list_head vec[TVR_SIZE];
};

可见它们实际上就是类型为 struct list_head 的数组,其中 TVN_SIZE 和 TVR_SIZE 在系统没有配置宏 CONFIG_BASE_SMALL 时分别被定义为64和256。


从图中可以清楚地看出:软件时钟( struct timer_list ,在图中由 timer 表示)以双向链表( struct list_head )的形式,按照它们的到期时间保存相应的( tv1~tv5 )中。tv1 中保存了相对于 timer_jiffies 下256个 tick 时间内到期的所有软件时钟; tv2 中保存了相对于 timer_jiffies 下256*64个 tick 时间内到期的所有软件时钟; tv3 中保存了相对于 timer_jiffies 下256*64*64个 tick 时间内到期的所有软件时钟; tv4 中保存了相对于 timer_jiffies 下256*64*64*64个 tick 时间内到期的所有软件时钟; tv5 中保存了相对于 timer_jiffies 下256*64*64*64*64个 tick 时间内到期的所有软件时钟。具体的说,从静态的角度看,假设 timer_jiffies 为0,那么 tv1[0] 保存着当前到期(到期时间等于 timer_jiffies )的软件时钟(需要马上被处理), tv1[1] 保存着下一个 tick 到达时,到期的所有软件时钟, tv1[n] (0<= n <=255)保存着下 n 个 tick 到达时,到期的所有软件时钟。而 tv2[0] 则保存着下256到511个 tick 之间到期所有软件时钟, tv2[1] 保存着下512到767个 tick 之间到期的所有软件时钟, tv2[n] (0<= n <=63)保存着下256*(n+1)到256*(n+2)-1个 tick 之间到达的所有软件时钟。 tv3~tv5 依次类推。


注:本章主要摘自IBM资料,下章主要对软时钟中断代码分析


资料:http://www.ibm.com/developerworks/cn/linux/l-cn-clocks/index.html


TIMER_INITIALIZER():
1):TIMER_INITIALIZER()用来声明一个定时器,它的定义如下:
#define TIMER_INITIALIZER(_function, _expires, _data) {         \
                .function = (_function),                        \            
                .expires = (_expires),                          \
                .data = (_data),                                \
                .base = &boot_tvec_bases,                       \
        }

注:在上章对里面的数据结构已经解释过了


2): mod_timer():修改定时器的到时时间
int mod_timer(struct timer_list *timer, unsigned long expires)
{
    //如果该定时器没有定义fuction
        BUG_ON(!timer->function);

        timer_stats_timer_set_start_info(timer);
        /*
         * This is a common optimization triggered by the
         * networking code - if the timer is re-modified
         * to be the same thing then just return:
         */
         //如果要调整的时间就是定时器的定时时间而且已经被激活,则直接返回
        if (timer->expires == expires && timer_pending(timer))
                return 1;
    //调用__mod_timer().呆会再给出分析
        return __mod_timer(timer, expires);
}


从代码可以看出,如果所给的要修改的时间等于定时器原来的时间并且定时器现在正处于活动状态,则不修改,返回1,否则修改定时器时间,返回0。mod_timer()是一个非有效的更新处于活动状态的定时器的时间的方法,如果定时器处于非活动状态,则会激活定时器。在功能上,mod_timer()等价于:

3): add_timer()用来将定时器挂载到定时软中断队列,激活该定时器
static inline void add_timer(struct timer_list *timer)
{
        BUG_ON(timer_pending(timer));
        __mod_timer(timer, timer->expires);
}

可以看到mod_timer与add_timer 最后都会调用__mod_timer().为了分析这个函数。

下面转入__mod_timer()的代码了:
int __mod_timer(struct timer_list *timer, unsigned long expires)
{
        tvec_base_t *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;
        }
    //取得当前CPU对应的tvec_bases
        new_base = __get_cpu_var(tvec_bases);

        if (base != new_base) {
                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);
        spin_unlock_irqrestore(&base->lock, flags);

        return ret;
}

代码解析:

取得软件时钟所在 base 上的同步锁( struct tvec_base 变量中的自旋锁),并返回该软件时钟的 base ,保存在 base 变量中
如果该软件时钟处在 pending 状态(在 base 中,准备执行),则调用detach_timer()函数将该定时器从它原来所属的链表中删除。
取得本 CPU 上的 base 指针(类型为 struct tvec_base* ),保存在 new_base 中
如果 base 和 new_base 不一样,也就是说软件时钟发生了迁移(从一个 CPU 中移到了另一个 CPU 上),那么如果该软件时钟的处理函数当前没有在迁移之前的那个 CPU 上运行,则先将软件时钟的 base 设置为 NULL ,然后再将该软件时钟的 base 设置为 new_base 。否则,跳到5。
设置软件时钟的到期时间。
调用 internal_add_timer 函数将软件时钟添加到软件时钟的 base 中(本 CPU 的 base )



4)internal_add_timer()的代码如下:

static void internal_add_timer(tvec_base_t *base, struct timer_list *timer)
{
        unsigned long expires = timer->expires;
        unsigned long idx = expires - base->timer_jiffies;
        struct list_head *vec;

        if (idx < TVR_SIZE) {
                int i = expires & TVR_MASK;
                vec = base->tv1.vec + i;
        } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
                int i = (expires >> TVR_BITS) & TVN_MASK;
                vec = base->tv2.vec + i;
        } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
                int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
                vec = base->tv3.vec + i;
        } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
                int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
                vec = base->tv4.vec + i;
        } else if ((signed long) idx < 0) {
                /*
                 * Can happen if you add a timer with expires == jiffies,
                 * or you set a timer to go off in the past
                 */
                vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
        } else {
                int i;
                /* If the timeout is larger than 0xffffffff on 64-bit
                 * architectures then we use the maximum timeout:
                 */
                if (idx > 0xffffffffUL) {
                        idx = 0xffffffffUL;
                        expires = idx + base->timer_jiffies;
                }
                i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
                vec = base->tv5.vec + i;
        }
        /*
         * Timers are FIFO:
         */
        list_add_tail(&timer->entry, vec);
}



* 计算该软件时钟的到期时间和 timer_jiffies (当前正在处理的软件时钟的到期时间)的差值,作为索引保存到 idx 变量中。
* 判断 idx 所在的区间,在
          o [0, 对象12]或者( 对象13, 0)(该软件时钟已经到期),则将要添加到 tv1 中
          o [对象14, 对象15],则将要添加到 tv2 中
          o [对象16, 对象17],则将要添加到 tv3 中
          o [对象18, 对象19],则将要添加到 tv4 中
          o [对象20, 对象21),则将要添加到 tv5 中,但实际上最大值为 0xffffffffUL
* 计算所要加入的具体位置(哪个链表中,即 tv1~tv5 的哪个子链表)
* 最后将其添加到相应的链表中


从这个函数可以得知,内核中是按照软件时钟到期时间的相对值(相对于 timer_jiffies 的值)将软件时钟添加到软件时钟所在的 base 中的。

5):定时器更新
每过一个HZ,就会检查当前是否有定时器的定时器时间到达.如果有,运行它所注册的函数,再将其删除.为了分析这一过程,我们先从定时器系统的初始化看起.
asmlinkage void __init start_kernel(void)
{
         ……
         init_timers();
         ……
}
Init_timers()的定义如下:
void __init init_timers(void)                                                               
{       
        int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
                                (void *)(long)smp_processor_id());
        
        init_timer_stats();
        
        BUG_ON(err == NOTIFY_BAD);
        register_cpu_notifier(&timers_nb);
        //注册TIMER_SOFTIRQ软中断
        open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);
} 

我们在前面分析过,每当时钟当断函数到来的时候,就会打开定时器的软中断.运行其软中断函数.run_timer_softirq()
代码如下:
static void run_timer_softirq(struct softirq_action *h)                                     
{
    ////取得当于CPU的tvec_base_t结构
        tvec_base_t *base = __get_cpu_var(tvec_bases);

        hrtimer_run_queues();
    //如果jiffies > base->timer_jiffies
        if (time_after_eq(jiffies, base->timer_jiffies))
                __run_timers(base);
}

__run_timers()代码如下:

static inline void __run_timers(struct tvec_base *base)
{
    ……
    spin_lock_irq(&base->lock);
    while (time_after_eq(jiffies, base->timer_jiffies)) {
        ……
        int index = base->timer_jiffies & TVR_MASK;
        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));
        ++base->timer_jiffies;
        list_replace_init(base->tv1.vec + index, &work_list);
        while (!list_empty(head)) {
            ……
            timer = list_first_entry(head, struct timer_list,entry);
            fn = timer->function;
            data = timer->data;
            ……
            set_running_timer(base, timer);
            detach_timer(timer, 1);
            spin_unlock_irq(&base->lock);
            {
                int preempt_count = preempt_count();
                fn(data);
                ……
            }
            spin_lock_irq(&base->lock);
        }
    }
    set_running_timer(base, NULL);
    spin_unlock_irq(&base->lock);
}


代码解析:
获得 base 的同步锁
如果 jiffies 大于等于 timer_jiffies (当前正要处理的软件时钟的到期时间,说明可能有软件时钟到期了),就一直运行3~7,否则跳转至8
计算得到 tv1 的索引,该索引指明当前到期的软件时钟所在 tv1 中的链表,代码:

int index = base->timer_jiffies & TVR_MASK;


调用 cascade 函数对软件时钟进行必要的调整(稍后会介绍调整的过程)
使得 timer_jiffies 的数值增加1
取出相应的软件时钟链表
遍历该链表,对每个元素进行如下操作

    * 设置当前软件时钟为 base 中正在运行的软件时钟(即保存当前软件时钟到 base-> running_timer 成员中)
    * 将当前软件时钟从链表中删除,即卸载该软件时钟
    * 释放锁,执行软件时钟处理程序
    * 再次获得锁

设置当前 base 中不存在正在运行的软件时钟
释放锁



static int cascade(struct tvec_base *base, struct tvec *tv, int index)
{
    struct timer_list *timer, *tmp;
    struct list_head tv_list;
    list_replace_init(tv->vec + index, &tv_list);
    list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
        ……
        internal_add_timer(base, timer);
    }
    return index;
}
该函数根据索引,取出相应的 tv ( tv2~tv5 )中的链表,然后遍历链表每一个元素。按照其到期时间重新将软件时钟加入到软件时钟的 base 中。该函数返回 tv 中被调整的链表索引值

 
6):del_timer()删除定时器
//删除一个timer
int del_timer(struct timer_list *timer)
{
        tvec_base_t *base;
        unsigned long flags;
        int ret = 0;

        timer_stats_timer_clear_start_info(timer);
        if (timer_pending(timer)) {
                base = lock_timer_base(timer, &flags);
                if (timer_pending(timer)) 
                {
                    //将timer从链表中删除
                        detach_timer(timer, 1);
                        ret = 1;
                }
                spin_unlock_irqrestore(&base->lock, flags);
        }

        return ret;
}
被激活或未被激活的定时器都可以用该函数,如果定时器未被激活,该函数返回0;否则返回1。
注意,不需为已经超时定时器调用该函数,因为会被自动调用。删除定时器时需要等待其他cpu运行该定时器处理器程序都退出。

7): del_timer_sync()有竞争情况下的定时器删除
int del_timer_sync(struct timer_list *timer)
{
    for (;;) {
        int ret = try_to_del_timer_sync(timer);
        if (ret >= 0)
            return ret;
        cpu_relax();
    }
}

del_timer_sync 函数无限循环试图卸载该软件时钟,直到该软件时钟能够被成功卸载。从其实现中可以看出:如果一个软件时钟的处理函数正在执行时,对其的卸载操作将会失败。一直等到软件时钟的处理函数运行结束后,卸载操作才会成功。这样避免了在 SMP 系统中一个 CPU 正在执行软件时钟的处理函数,而另一个 CPU 则要将该软件时钟卸载所引发的问题。
定时器部份到这里就介绍完了.为了管理定时器.内核用了一个很巧妙的数据结构.值得好好的体会.


注:下章,介绍定时器的延迟。
定时器延迟

schedule_timeout()函数,会使需要延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行。

用法如下:
//将任务设置为可中断睡眠状态
set_current_state(TASK_INTERRUPTIBLE);

//小睡,“S”秒后醒
schedule_timeout(s*HZ)


fastcall signed long __sched schedule_timeout(signed long timeout)
{
        struct timer_list timer;
        unsigned long expire;

        switch (timeout)
        {
        case MAX_SCHEDULE_TIMEOUT:
                /*
                 * These two special cases are useful to be comfortable
                 * in the caller. Nothing more. We could take
                 * MAX_SCHEDULE_TIMEOUT from one of the negative value
                 * but I' d like to return a valid offset (>=0) to allow
                 * the caller to do everything it want with the retval.
                 */
                schedule();
                goto out;
        default:
                /*
                 * Another bit of PARANOID. Note that the retval will be
                 * 0 since no piece of kernel is supposed to do a check
                 * for a negative retval of schedule_timeout() (since it
                 * should never happens anyway). You just have the printk()
                 * that will tell you if something is gone wrong and where.
                 */
                if (timeout < 0) {
                        printk(KERN_ERR "schedule_timeout: wrong timeout "
                                "value %lx\n", timeout);
                        dump_stack();
                        current->state = TASK_RUNNING;
                        goto out;
                }
        }

        expire = timeout + jiffies;

        setup_timer(&timer, process_timeout, (unsigned long)current);
        __mod_timer(&timer, expire);
        schedule();
        del_singleshot_timer_sync(&timer);                                                                                                                   

        timeout = expire - jiffies;

 out:
        return timeout < 0 ? 0 : timeout;
}



函数 schedule_timeout 定义了一个软件时钟变量 timer ,在计算到期时间后初始化这个软件时钟:设置软件时钟当时间到期时的处理函数为 process_timeout ,参数为当前进程描述符,设置软件时钟的到期时间为 expire 。之后调用 schedule() 函数。此时当前进程睡眠,交出执行权,内核调用其它进程运行。但内核在每一个时钟中断处理结束后都要检测这个软件时钟是否到期。如果到期,将调用 process_timeout 函数,参数为睡眠的那个进程描述符。
其中MAX_SCHEDULE_TIMEOUT是用来检查任务是否无限期睡眠,如果是,函数不会为它设置定时器,这时调度会立即执行。




setup_timer()函数对定时器的初始化

static inline void setup_timer(struct timer_list * timer,

void (*function)(unsigned long),

unsigned long data)

{

timer->function = function;

timer->data = data;

init_timer(timer);

} 
其中对base字段的赋值是调用了init_timer()函数。




process_timeout()函数
static void process_timeout(unsigned long __data)
{
    wake_up_process((struct task_struct *)__data);
}


函数 process_timeout 直接调用 wake_up_process 将进程唤醒。当内核重新调用该进程执行时,该进程继续执行 schedule_timeout 函数,执行流则从 schedule 函数中返回,之后调用 del_singleshot_timer_sync 函数将软件时钟卸载,然后函数 schedule_timeout 结束。


注:下章,做个总结。毕竟看了那么多,得总结下。看来我有个好习惯也,呵呵。
终于找点时间来总结时钟中断,呵呵,爱上linux了。

时间管理在内核占有非常重要的地位。相对于事件驱动而言,内核中大量的函数都是基于时间驱动的。
其中有些函数是周期执行的,像调度程序中的运行队列进行平衡调整。

时钟是整个操作系统的脉搏,它为进程的时间片调度,定时事件提供了依据.另外,用户空间的很多操作都依赖于时钟,例如select.poll,make. 操作系统管理的时间为分两种,一种称为当前时间,也即我们日常生活所用的时间.这个时间一般保存在CMOS中.主板中有特定的芯片为其提供计时依据.另外一种时间称为相对时间.例如系统运行时间.显然对计算机而然,相对时间比当前时间更为重要.

内核通过控制时钟中断维护时间,且时钟中断对于管理操作系统相当的重要,大量内核函数的生命周期
都离不开流逝的时间的控制。时钟中断的作用:

1.更新系统运行时间
2.更新实际时间
3.在SMP系统中,均衡调度程序中各处理器上的运行队列。如果运行队列负载不均衡的话,尽量使
  它们均衡
4.检查当前进程是否耗尽自己的时间片。如果用尽,就重新进行调度
5.运行超时的动态定时器
6.更新资源消耗和处理器的时间统计值


时钟中断处理程序具体执行的工作:

1.获得xtime_lock锁,以便对访问jiffies_64和墙上时间进行更新<xtime_lock防止多cpu的竞态访问>
2.需要时应答或重新设置系统时钟
3.调用do_timer(),执行以下工作:
          1>给jiffies_64变量增加1;
          2>更新资源消耗统计值,比如当前进程所消耗的系统时间和用户时间
          3>执行已经到期的动态定时器
          4>执行scheduler_tick(),函数减少当前运行进程的时间片计数值并且
            设置need_resched标志
          5>更新实际时间,存放在xtime变量中,计算平均负载值。
【Linux】调度器底层原理深入探索 本文深入解析了Linux调度器的底层原理。首先介绍了调度基础概念,包括调度目标、进程状态和调度时机。接着详细讲解了Linux的多种调度策略(SCHED_OTHER/FIFO/RR/DEADLINE等)及其适用场景。重点剖析了CFS(完全公平调度器)的核心机制,包括虚拟运行时间(vruntime)的计算方法、红黑树管理就绪队列的数据结构,以及nice值与权重的映射关系。文章还对比了Linux调度器的发展历程,从早期的O(n)调度器到现代CFS调度器的演进过程,帮助读者全面理解Linux进程调度的实现原理。 阅读详情

相关推荐

elasticsearch-6.5.4集群部署(图文详细)及常见错误

目录 服务器规划 部署包下载 服务器初始化(所有节点都要操作) 内核参数修改 /etc/security/limits.conf修改 /etc/sysctl.conf文件修改 禁用selinux 关闭防火墙 创建es用户 安装jdk es部署 单节点(节点1)部署 部署包上传解压 配置文件修改 修改文件属组 部署包分发 单节点(节点2)部署 配置文件修改 修改文件属组 单节点(节点3)部署 配置文件修改 修改文件属组 启动ES集群 验证 单节点验证 查看集

夜灬狼丶 1975

ThreadX内核源码分析 - ports线程上下文相关代码分析(arm)

1、ports源码介绍 内核与cpu相关的关键代码基本都是用汇编语言实现的,c语言可能实现不了或者不好编写。 ThreadX官网针对ARM9 gcc的移植代码在threadx-6.1.2_rel\ports\arm9\gnu\src目录下,ThreadX文件命名规则基本是以该文件包含的函数名命名的(函数名多了一个"_"前缀,文件名里面没有"_"前缀),每个源文件通常只实现一个函数;ports代码目录如下: tx_thread_context_restore.S是_tx_thread_context

arm7star的博客 2739

基于企业微信客服与GPT-3构建合规微信AI助手:从原理到部署实践

在人工智能与即时通讯融合的浪潮中,自然语言处理(NLP)与API集成成为构建智能对话系统的核心技术。其原理在于通过API网关接收用户输入,调用云端大语言模型进行处理,再将结果返回给用户,实现自动化交互。这项技术的核心价值在于将前沿AI能力无缝嵌入到日常高频应用场景中,极大地提升了客服自动化、智能问答与个性化服务的效率与体验。企业微信作为官方认可的企服平台,其开放的客服API接口为开发者提供了稳定、合规的消息通道,是连接微信生态与外部AI服务的理想桥梁。本文聚焦于如何利用企业微信客服API与OpenAI的GP

weixin_28727943的博客 626

ThreadX学习(5)——系统时钟和定时器

ThreadX学习(4)——系统时钟和定时器学习参考内部系统时钟作用API1.tx_time_set2.tx_time_get应用程序定时器 学习参考 《Real-Time Embedded Multithreading: Using ThreadX and ARM》 安富莱_STM32-V7开发板ThreadX内核教程(V0.7) 内部系统时钟 作用 ThreadX提供了一个连续递增的32位tick计数器 _tx_timer_system_clock 内部系统时钟。 应用程序初始化期间将内部系统时钟设

weixin_43739110的博客 4001

在Ubuntu 22.04上从零搭建FVCOM环境:手把手搞定Linux、Intel编译器与NetCDF库

本文详细指导在Ubuntu 22.04上从零搭建FVCOM环境,涵盖Linux系统配置、Intel编译器安装、NetCDF库编译及常见问题解决。通过手把手教程,帮助科研人员高效完成水动力数值模拟环境的部署,特别适合水环境研究领域的初学者。

weixin_30613727的博客 650

CANoe基础讲解01:从零开始使用CANoe轻松搭建你的第一个CAN网络

本文是一篇CANoe软件入门实操指南,逐步演示了如何从零创建并运行一个CAN FD仿真工程。内容包括:启动软件、配置硬件通道与网络、加载DBC数据库、使用CAPL编写仿真节点发送CAN报文,最终启动测量并在Trace窗口中查看通信结果。通过七个清晰步骤,帮助初学者快速掌握CANoe基本操作,完成首个可运行的仿真网络搭建

车载网络小D的博客 1571

timer interrupt /tx_timin.s

汇编写的,下文为同等操作的C 1.系统时钟++ 2.若有剩余的时间片 (1) 时间片-- (2) 若当前时间片耗尽 _tx_timer_expired_time_slice=true 3.定时器 (1) 若有定时器需要处理 _tx_timer_expired=true (2)若没有定时器需要处理 _tx_timer_curre...

qq_45669254的博客 283

【SAP PO】SAP PO 接口配置完整教程之一RFC服务发布

SAP RFC开发,PO接口配置

XLevon的博客 2万+

linux 2.6.23时钟中断与调度分析,进程调度Linux内核分析ppt课件

《进程调度Linux内核分析ppt课件》由会员分享,可在线阅读,更多相关《进程调度Linux内核分析ppt课件(26页珍藏版)》请在人人文库网上搜索。1、Linux操作系统分析,陈香兰 Fall 2010,12/25/2020,Linux操作系统分析,2,12/25/2020,Linux操作系统分析,3,12/25/2020,Linux操作系统分析,4,第二种分类 交互式进程(interactiv...

weixin_39673972的博客 234

GEE:连续变化检测 CCDC 提取断点信息(干扰时间、干扰幅度、干扰次数)

本文将介绍在 Google Earth Engine (GEE) 平台上连续变化检测与分类(Continuous Change Detection and Classification, CCDC)提取扰动时间、干扰幅度、干扰次数信息,并下载到本地的代码。

养乐多的博客 1943

黑猫带你学UFS协议第1篇:全网最全UFS协议中文详讲,这份学习框架图,你值得拥有!!!(持续更新中...)

本文由黑猫学长撰写,深入解析UFS协议,覆盖从基础概念到高级特性的全方位教程。适合存储芯片行业从业者和嵌入式开发者,旨在帮助读者快速掌握UFS协议,适用于高端智能手机、平板等智能设备。预计15万字,涵盖UFS 3.1协议,讲解UFS协议栈、物理层、链路层、传输层等内容。

黑猫奥利奥的博客 3687

linux 2.6.23时钟中断与调度分析,linux2.6CFS调度算法分析(13页)-原创力文档

linux2.6 CFS 调度算法分析概述CFS( completely fair schedule )是最终被内核采纳的调度器。它从 RSDL/SD 中吸取了完全公平的思想,不再跟 踪进程的睡眠时间,也不再企图区分交互式进程。它将所有的进程都统一对待,这就是公平的含义。 CFS 的算法和实现都相当简单, 众多的测试表明其性能也非常优越。CFS都相当简单, 众多的测试表明其性能也非常优越。CFS背...

weixin_28728495的博客 232

Openclaw错误分析与解决方案之:All models are temporarily rate-limited. Please try again in a few minutes

Openclaw错误分析与解决方案之: All models are temporarily rate-limited. Please try again in a few minutes.

多媒体编程、网络编程、系统编程、网络安全编程、驱动编程 1617

linux 2.6.23时钟中断与调度分析(4)

TIMER_INITIALIZER():1):TIMER_INITIALIZER()用来声明一个定时器,它的定义如下:#define TIMER_INITIALIZER(_function, _expires, _data) {         /                .function = (_function),                        /          

1273

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

linux内核调度问题分析

假如内核只有3个线程,线程0创建线程1和线程2.当系统时钟到来时,时钟中断处理函数会检查是否有进程需要调度。当有进程需要调度时,调度器会选择线程1或者线程2。start_kernel运行在线程0里,线程0创建线程1和线程2。函数调用关系start_kernel()->kernel_debug()->do_fork 创建新线程,并把新线程添加到调度器的就绪队列中。线程0创建线程1和线程2后,进入while线程,线程0不会退出,等待被调度出去。1、产生时钟中断。处理器采用定时器来周期性地执行。

2882

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Linux调度器发展概述

刚刚发布的 2.6.23 内核中包含了一个重要的变化,用CFS替代了以前的调度器。CFS 被合并到 mainline 之前,关于内核调度器还有一个重要的 patch:RSDL。最终 2.6.23 决定将 CFS 合并到 mainline 而放弃了 RSDL。为什么要引入新的调度器,CFS 和 RSDL 有什么联系和区别?本文试图对内核调度算法的发展历史做一个简要介绍,希望能对上述问题的理解有所帮助

orange_os的专栏 2498

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

linux调度器源码分析 - 运行(四)

linux调度器源码分析 - 运行(四) 本文为原创,转载请注明:http://www.cnblogs.com/tolimit/  http://www.cnblogs.com/tolimit/p/4335681.html 引言   之前的文章已经将调度器的数据结构、初始化、加入进程都进行了分析,这篇文章将主要说明调度器是如何在程序稳定运行的情况下进行进程调度的。  

zdy0_2004的专栏 389

Linux 完全公平调度算法

怎么向可运行队列添加调度的进程和怎么从可运行队列中获取下一个可运行的进程,那么 Linux 内核在什么时候会进行进程调度呢?如此类推,由于每个时间片都是相等的,所以理论上每个进程都能获得相同的CPU运行时间。函数后,就可以把进程调度实体插入到运行队列的红黑树中。例如,调度周期为 30ms,进程A的权重为 1,而进程B的权重为 2。前面介绍过,红黑树的最左端节点就是虚拟运行时间最少的进程,所以。成员,其保存了可运行进程队列的根节点(红黑树的根节点)。从上面的公式可以看出,在一个调度周期里,所有进程的。

m0_74282605的博客 248

Linux内核CFS完全公平调度器:从vruntime到负载均衡的深度实现分析

CFS核心机制是vruntime归一化:实际运行时间乘以(NICE_0_LOAD/weight),将不同优先级进程统一到虚拟时间维度进行公平比较红黑树以vruntime为键值组织可运行进程,最左节点总是下一个被调度的进程,配合最左节点缓存实现O(1)选取调度周期默认6ms(nr_running≤8),超8个时按进程数等比例增长,保证每个进程最小粒度0.75ms负载均衡使用PELT几何衰减累积负载,半衰期32ms(y≈0.9785),通过软中断触发进程在CPU间的迁移。

jiang_style的博客 4575

(原创)linux内核进程调度以及定时器实现机制

一、2.6版以前内核进程调度机制简介Linux的进程管理由进程控制块、进程调度、中断处理、任务队列、定时器、bottom half队列、系统调用、进程通信等等部分组成。进程调用分为实时进程调度和非实时进程调度两种。前者调度时,可以采用基于动态优先级的轮转法(RR),也可以采用先进现出算法(FIFO)。后者调度时,一律采用基于动态优先级的轮转法。某个进程采用何种调度算法由改进程的进程控制块中

峥嵘岁月 1万+

Linux内核进程调度以及定时器实现机制(转)

摘要】本文简单介绍了任务的各种状态和PCB的结构,分析了几种任务调度策略,详解了schedule,并分析了如何进行进程上下文切换;随后分析了2.6内核如何优化了任务调度算法;最后介绍了内核定时器的实现机制和系统调用的实现过程。【关键词】进程控制块PCB,RR,FIFO,内核调度算法,任务切换,内核定时,timer,软中断softirq,系统调用  一、2.6版以前内核进程调度机制简

ctwei85的专栏 1696

Linux源码剖析—— 完全公平调度算法

/ 运行队列中的进程数// 当前时钟// 用于修证虚拟运行时间​// 红黑树的根节点// 缓存红黑树最左端节点, 用于快速获取最小节点...对于。

白话机器学习 594

Linux内核实现完全公平调度算法

如果没了解过操作系统原理的可能不了解这个东西,但是没关系,不了解可以把他当成是定时器就好了,就是每隔固定一段时间会调用一个回调函数来处理这个事件。怎么向可运行队列添加调度的进程和怎么从可运行队列中获取下一个可运行的进程,那么 Linux 内核在什么时候会进行进程调度呢?如此类推,由于每个时间片都是相等的,所以理论上每个进程都能获得相同的CPU运行时间。前面介绍过,红黑树的最左端节点就是虚拟运行时间最少的进程,所以。成员,其保存了可运行进程队列的根节点(红黑树的根节点)。

嵌入式Linux内核的博客 815

Linux进程调度策略的发展和演变--Linux进程的管理与调度(十六)

1 前言 1.1 进程调度 内存中保存了对每个进程的唯一描述, 并通过若干结构与其他进程连接起来. 调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换. 1.2 进程的分类 linux把进程区分为实时进程和非实时进程, 其中非实时进程进一步划分为交互式进程和批处理进程 在linu...

~~ LINUX ~~ 389
上一篇: gpio_direction_output vs gpio_set_value之间的使用关系
下一篇: Android应用程序获得root权限
计算机与认知
博客等级 码龄14年 31粉丝 19原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值