定时器原理_定时器原理图 - CSDN
  • 实现定时器原理

    2019-11-19 10:29:33
    要想实现定时如,定时异步请求网络数据什么的,可以简单地使用while循环+ boolean标志位+ if 判断就行了。

    要想实现定时如,定时异步请求网络数据什么的,可以简单地使用while循环+ boolean标志位+ if 判断就行了。

    展开全文
  • 定时器的实现依赖的是CPU时钟中断,时钟中断的精度就决定定时器精度的极限。一个时钟中断源如何实现多个定时器呢?对于内核,简单来说就是用特定的数据结构管理众多的定时器,在时钟中断处理中判断哪些定时器超时,...

    定时器的实现原理

    定时器的实现依赖的是CPU时钟中断,时钟中断的精度就决定定时器精度的极限。一个时钟中断源如何实现多个定时器呢?对于内核,简单来说就是用特定的数据结构管理众多的定时器,在时钟中断处理中判断哪些定时器超时,然后执行超时处理动作。而用户空间程序不直接感知CPU时钟中断,通过感知内核的信号、IO事件、调度,间接依赖时钟中断。用软件来实现动态定时器常用数据结构有:时间轮、最小堆和红黑树。下面就是一些知名的实现:

    Linux内核定时器相关(Linux v4.9.7, x86体系架构)的一些相关代码:

    内核启动注册时钟中断

    // @file: arch/x86/kernel/time.c - Linux 4.9.7
    // 内核init阶段注册时钟中断处理函数
    static struct irqaction irq0  = {
        .handler = timer_interrupt,
        .flags = IRQF_NOBALANCING | IRQF_IRQPOLL | IRQF_TIMER,
        .name = "timer"
    };
    
    void __init setup_default_timer_irq(void)
    {
        if (!nr_legacy_irqs())
            return;
        setup_irq(0, &irq0);
    }
    
    // Default timer interrupt handler for PIT/HPET
    static irqreturn_t timer_interrupt(int irq, void *dev_id)
    {
        // 调用体系架构无关的时钟处理流程
        global_clock_event->event_handler(global_clock_event);
        return IRQ_HANDLED;
    }
    

    内核时钟中断处理流程

    // @file: kernel/time/timer.c - Linux 4.9.7
    /*
     * Called from the timer interrupt handler to charge one tick to the current
     * process.  user_tick is 1 if the tick is user time, 0 for system.
     */
    void update_process_times(int user_tick)
    {
        struct task_struct *p = current;
    
        /* Note: this timer irq context must be accounted for as well. */
        account_process_tick(p, user_tick);
        run_local_timers();
        rcu_check_callbacks(user_tick);
    #ifdef CONFIG_IRQ_WORK
        if (in_irq())
            irq_work_tick();
    #endif
        scheduler_tick();
        run_posix_cpu_timers(p);
    }
    
    /*
     * Called by the local, per-CPU timer interrupt on SMP.
     */
    void run_local_timers(void)
    {
        struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
    
        hrtimer_run_queues();
        /* Raise the softirq only if required. */
        if (time_before(jiffies, base->clk)) {
            if (!IS_ENABLED(CONFIG_NO_HZ_COMMON) || !base->nohz_active)
                return;
            /* CPU is awake, so check the deferrable base. */
            base++;
            if (time_before(jiffies, base->clk))
                return;
        }
        raise_softirq(TIMER_SOFTIRQ); // 标记一个软中断去处理所有到期的定时器
    }
    

    内核定时器时间轮算法

    单层时间轮算法的原理比较简单:用一个数组表示时间轮,每个时钟周期,时间轮 current 往后走一个格,并处理挂在这个格子的定时器链表,如果超时则进行超时动作处理,然后删除定时器,没有则剩余轮数减一。原理如图:
    这里写图片描述
    Linux 内核则采用的是 Hierarchy 时间轮算法,Hierarchy 时间轮将单一的 bucket 数组分成了几个不同的数组,每个数组表示不同的时间精度,Linux 内核中用 jiffies 记录时间,jiffies记录了系统启动以来经过了多少tick。下面是Linux 4.9的一些代码:

    // @file: kernel/time/timer.c - Linux 4.9.7
    /*
     * The timer wheel has LVL_DEPTH array levels. Each level provides an array of
     * LVL_SIZE buckets. Each level is driven by its own clock and therefor each
     * level has a different granularity.
     */
    
    /* Size of each clock level */
    #define LVL_BITS    6
    #define LVL_SIZE    (1UL << LVL_BITS)
    
    /* Level depth */
    #if HZ > 100
    # define LVL_DEPTH  9
    # else
    # define LVL_DEPTH  8
    #endif
    
    #define WHEEL_SIZE  (LVL_SIZE * LVL_DEPTH)
    
    struct timer_base {
        spinlock_t      lock;
        struct timer_list   *running_timer;
        unsigned long       clk;
        unsigned long       next_expiry;
        unsigned int        cpu;
        bool            migration_enabled;
        bool            nohz_active;
        bool            is_idle;
        DECLARE_BITMAP(pending_map, WHEEL_SIZE);
        struct hlist_head   vectors[WHEEL_SIZE];
    } ____cacheline_aligned;
    

    Hierarchy 时间轮的原理大致如下,下面是一个时分秒的Hierarchy时间轮,不同于Linux内核的实现,但原理类似。对于时分秒三级时间轮,每个时间轮都维护一个cursor,新建一个timer时,要挂在合适的格子,剩余轮数以及时间都要记录,到期判断超时并调整位置。原理图大致如下:
    这里写图片描述

    定时器的使用方法

    在Linux 用户空间程序开发中,常用的定期器可以分为两类:

    1. 执行一次的单次定时器 single-short;
    2. 循环执行的周期定时器 Repeating Timer;

    其中,Repeating Timer 可以通过在Single-Shot Timer 终止之后,重新再注册到定时器系统里来实现。当一个进程需要使用大量定时器时,同样利用时间轮、最小堆或红黑树等结构来管理定时器。而时钟周期来源则需要借助系统调用,最终还是从时钟中断。Linux用户空间程序的定时器可用下面方法来实现:

    • 通过alarm()setitimer()系统调用,非阻塞异步,配合SIGALRM信号处理;
    • 通过select()nanosleep()系统调用,阻塞调用,往往需要新建一个线程;
    • 通过timefd()调用,基于文件描述符,可以被用于 select/poll 的应用场景;
    • 通过RTC机制, 利用系统硬件提供的Real Time Clock机制, 计时非常精确;

    上面方法没提sleep(),因为Linux中并没有系统调用sleep(),sleep()是在库函数中实现,是通过调用alarm()来设定报警时间,调用sigsuspend()将进程挂起在信号SIGALARM上,而且sleep()也只能精确到秒级上,精度不行。当使用阻塞调用作为定时周期来源时,可以单独启一个线程用来管理所有定时器,当定时器超时的时候,向业务线程发送定时器消息即可。

    一个基于时间轮的定时器简单实现

    #include <stdio.h>
    #include <signal.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    #define TIME_WHEEL_SIZE 8
    
    typedef void (*func)(int data);
    
    struct timer_node {
        struct timer_node *next;
        int rotation;
        func proc;
        int data;
    };
    
    struct timer_wheel {
        struct timer_node *slot[TIME_WHEEL_SIZE];
        int current;
    };
    
    struct timer_wheel timer = {{0}, 0};
    
    void tick(int signo)
    {
        // 使用二级指针删进行单链表的删除
        struct timer_node **cur = &timer.slot[timer.current];
        while (*cur) {
            struct timer_node *curr = *cur;
            if (curr->rotation > 0) {
                curr->rotation--;
                cur = &curr->next;
            } else {
                curr->proc(curr->data);  // bug-fix: 与下面一样交换位置
                *cur = curr->next;
                free(curr);
            }
        }
        timer.current = (timer.current + 1) % TIME_WHEEL_SIZE;
        alarm(1);
    }
    
    void add_timer(int len, func action)
    {
        int pos = (len + timer.current) % TIME_WHEEL_SIZE;
        struct timer_node *node = malloc(sizeof(struct timer_node));
    
        // 插入到对应格子的链表头部即可, O(1)复杂度
        node->next = timer.slot[pos];
        timer.slot[pos] = node;
        node->rotation = len / TIME_WHEEL_SIZE;
        node->data = 0;
        node->proc = action;
    }
    
     // test case1: 1s循环定时器
    int g_sec = 0;
    void do_time1(int data)
    {
        printf("timer %s, %d\n", __FUNCTION__, g_sec++);
        add_timer(1, do_time1);
    }
    
    // test case2: 2s单次定时器
    void do_time2(int data)
    {
        printf("timer %s\n", __FUNCTION__);
    }
    
    // test case3: 9s循环定时器
    void do_time9(int data)
    {
        printf("timer %s\n", __FUNCTION__);
        add_timer(9, do_time9);
    }
    
    int main()
    {
        signal(SIGALRM, tick);
        alarm(1); // 1s的周期心跳
    
        // test
        add_timer(1, do_time1);
        add_timer(2, do_time2);
        add_timer(9, do_time9);
    
        while(1) pause();
        return 0;
    }
    

    在实际项目中,一个常用的做法是新起一个线程,专门管理定时器,定时来源使用rtc、select等比较精确的来源,定时器超时后向主要的work线程发消息即可,或者使用timefd接口。

    参考:

    展开全文
  • 通用定时器原理

    千次阅读 2016-09-26 14:59:50
    一,STM32三种定时器区别 2个高级定时器 4个通用定时器 2个基本定时器 二,定时器的计数模式向上计数模式: 计数器从0开始计数,当计数到自动装载值(TIMx_ARR)时产生向上溢出事件 向下计数模式: 计数器从自动装载值...

    一,STM32三种定时器区别

    三种定时器

     2个高级定时器
     4个通用定时器
     2个基本定时器
    

    二,定时器的计数模式

    定时器的三种工作模式

    向上计数模式:
        计数器从0开始计数,当计数到自动装载值(TIMx_ARR)时产生向上溢出事件
    向下计数模式:
        计数器从自动装载值(TIMx_ARR)开始向下计数,当计数到0时产生向下溢出事件
    中央对齐模式(向上/向下计数):
        计数器从0开始计数,到自动装载值(TIMx_ARR)-1时,产生计数器溢出事件,
        然后向下计数到1,产生计数器溢出事件,然后再从0开始重新计数
    

    三,通用定时器的介绍

    对于总线时钟有疑问请查看 STM32时钟系统

    通用定时器包括:TIM2,TIM3,TIM4,TIM5

    位于低速APB1总线上 
    16位定时器,支持向上,向下,中央对齐
    自动重装载寄存器TIMx_CNT
    16位可编程(1-65535.可实时修改)预分频器 TIMx_PSC
    4个独立通道(TIMx_CH1-CH4),彼此不影响
    独立通道作用:
        1,输入捕获
        2,输出比较
        3,PWM生成(边缘或中间对齐模式)
        4,单脉冲模式输出
    每个定时器有4个独立通道,每个通道同一时间只能使用输入捕获和输出比较的一种
    支持定时器互联
    STM32的每个通用定时器都是完全独立的,没有互相共享的任何资源
    

    产生中断和/DMA的事件:(6个独立的IRQ/DMA请求生成器)

    1,更新: 计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部外部触发)
    2,触发事件(计数器启动,停止,初始化,由内部/外部触发计数)
    3,输入捕获
    4,输出比较
    5,支持针对定位的增量(正交)编码器和霍尔传感电路
    6,触发输入作为外部时钟或按周期的电流管理
    

    STM32通用定时器被用于:

     测量输入信号的脉冲长度(输入捕获)或产生输出波形(输出比较和PWM)等
    
     使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整
    

    四,通用定时器框图

    通用定时器框图


    1,第一部分:时钟发生器

    这里写图片描述

    主要作用:产生时基单元的时钟


    1)时基单元的时钟来源来自内部时钟

    来自内部时钟

    来自内部时钟:
        触发控制器将内部APB1总线通过倍频得到的时钟作为时基单元的时钟来源
    

    2)时基单元的时钟来源来自外部时钟

    来自外部时钟

    来自外部时钟:
        外部引脚输入信号,TIMx_ETR(定时器2 3 4,定时器5没有这个寄存器),
        通过极性选择,边沿检测,和预分频器,再通过输入滤波产生时钟
        作为时基单元的时钟来源
    

    3)时基单元的时钟来源来自其他定时器

    来自其他定时器

    来自其他定时器:
        ITR0-ITR4内部触发输入口,通过定时器的级联,来自其他定时器的时钟,
        通过选择器,触发控制器,作为时基单元的时钟来源
        由其他定时器的TRGO,输入到ITR1-ITR4,定时器的级联
    

    4)时基单元的时钟来源来自外部通道

    这里写图片描述

    外部通道输入:
        通过选择器,通过触发控制器,作为时基单元的时钟来源
    

    总结:
        使用较多的应用场景是:内部时钟
    

    2,时基单元部分

    这里写图片描述

    CK_PSC时钟信号进行预分频,产生CK_CNT时钟为计数器最终时钟
    计数器在这个时钟的控制下进行计数,向上/向下/中央对齐计数是在触发控制器的相应寄存器中配置的
    当计数器向上计数到重装载值或向下计数到0将触发中断或DMA请求
    

    3,输入捕获部分

    输入捕获部分

    应用场景:计算脉冲宽度
        先设置为捕获上升沿,输入信号进行输入滤波(抗干扰)和边沿检测,检测到上升沿,捕获比较寄存器会将这个值记下来
        再设置为捕获下降沿,检测到下降沿,又记录下这个值,
        通过这两个值可以计算脉冲宽度
    每个定时器有4个独立通道,每个通道同一时间只能使用输入捕获和输出比较的一种
    

    4,输出比较部分

    输出比较部分

    PWM输出:
        设置捕获/比较寄存器为一个置,当计数器比这个值大输出高电平,比这个值小输出低电平
        这样可以产生一个波,通过调整占空比(捕获/比较寄存器的值),可以输出PWM
    
    展开全文
  • JavaScript是一门基于对象的弱类型语言,它作为浏览器脚本语言,主要用途是负责与页面的交互,以及操作DOM,它的执行环境是单线程的,默认情况JS是同步加载的,也就是 JS的加载是阻塞的,也就是说同一时间只能完成一...

     

    JavaScript是一门基于对象的弱类型语言,它作为浏览器脚本语言,主要用途是负责与页面的交互,以及操作DOM,它的执行环境是单线程的,默认情况JS是同步加载的,也就是 JS的加载是阻塞的,也就是说同一时间只能完成一件事,只能自上而下执行,万一上一行解析代码的时间很长,那么下面的代码就会被阻塞。对于用户而言,阻塞就意味着"卡死",这样就导致了很差的用户体验。

    为了解决这个问题,利用多核CPU的计算能力,于是出现了同步和异步

    同步操作,任务遵循队列顺序异步操作,就相当于并线了,因此异步任务不具有阻塞效应。同步任务都是在主线程中执行,形成了一个执行栈,直到主线程空闲时,才会去事件队列中查看是否有可执行的异步任务,如果有就推入主进程中。

    JS是通过回调函数实现异步的

    一旦用了setTimeout(),里面的回调函数就是异步代码,加入了任务队列,延时调用一个函数不马上执行,而是要等待主队列为空,并达到定的延时时间才会执行,而且只会执行一次。

    举个栗子:

     setTimeout(function(){
          console.log("1")
     },500)
          console.log("2")
     setTimeout(function(){
          console.log("3")
     },300)
      setTimeout(function(){
         console.log("5")
     },0)
         console.log("4")

     

    从输出结果中,我们就可以看书JS执行的顺序,2和4都为主队列的同步任务,一开始就执行,执行顺序是从上到下。

    setTimeout()中的函数,无论写在JS代码中的哪个位置,设置的时间即使是0,都为异步执行,在任务队列中,从主线程执行一开始,就进行计时,一旦主线程空闲,时间短的就立即加入到主线程开始执行,时间长的依然在等待。所以根据设定的等待时间,执行顺序为5,3,1

    利用setInterval()实现异步执行

    定时器的执行原理与延时器相似,只是不清除定时器,他就会反复执行。

    异步执行的两个必要条件就是:

    1.  主队列空闲     
    2.  到达执行时间

    通过上面的例子大家应该就懂了定时器的执行规则,下面我们再详细的分析一下为什么是这样的一个规则,他是与浏览器的执行规则有关系的。

    -------------------------------------回到主题,继续剖析------------------------------------ 

    浏览器的执行原理是什么呢?

    浏览器的多线程:

    JS是单线程的,但是对于浏览器来说JS的执行只不过是在浏览器众多现成中的一条,我们称之为JS引擎线程。而浏览器的其他线程则是通过JS引擎线程在执行到某个特定的功能之后指定给浏览器的对应线程

    同样类似的栗子:

    setTimeout(console.log('定时器!'),0);
    console.log("测试")

    这个结果大家应该都知道了,先打印测试字样,后打印定时器字样。

    原理:

    首先JS线程读取到setTimeout定时器,这个时候就会执行浏览器的线程,然后跳过定时器继续执行,这个时候你就看到了弹出框的内容为测试,然后因为定时器的时间为0,所以一执行定时器线程就会即可将弹出框为定时器字样的任务添加到主线程(JS引擎线程)的队列之后,等待JS引擎的调用,这个时候我们看到的结果是先弹出测试,然后再弹出定时器

    setTimeout我们可以再次定义为:

    在指定时间内, 将任务放入事件队列,等待js引擎空闲后被执行.

    -------------------------讲完原理还不够,定时器的this指向是啥?------------------------- 

        var name = 'my name is window';
        var obj = {
            name: 'my name is obj',
            fn: function () {
                // var that = this; 可以在此处定义一个this,改变this指向
                setTimeout(function () {
                    console.log(this.name);   //my name is window 定时器中this默认指向window
                    console.log(that.name)    //my name is obj 
                }, 1000)
                // 也可以使用箭头函数改变this指向
                // setTimeout(()=> {
                //     console.log(this.name);   //my name is obj
                // }, 1000)
            }    
        }
        obj.fn()

    在setTimeout内部,this绑定采用默认绑定规则,也就是说,在非严格模式下,this会指向window;而在严格模式下,this指向undefined

    学会定时器的原理后,来个小测试~~~~~ 

    for (var i = 0; i < 5; i++) {
    	console.log(i);
    	setTimeout(function timer() {
    		console.log(i);
    	}, i * 1000);
    }  //依次输出:0, 1, 2, 3, 4  接着输出5个5

    稍微解释一下啦:javascript是单线程语言,只有主线程上的所有同步任务执行完毕,主线程才会读取任务队列上的异步任务。for循环属于同步任务,而定时器属于异步任务。所以会在for循环结束之后才开始执行定时器的代码。因此会输出5个5。

    如果帮到你了,请点个赞呦~~~

    展开全文
  • 非常详细地介绍了系统时钟FCLK、HCLK、PCLK和定时器timer的原理、设置步骤使用方法。并提供了一个通俗易懂的例程。学习系统时钟和定时器,只要研究通透本文即可。
  • 51单片机定时器原理与使用

    万次阅读 多人点赞 2017-06-18 21:04:40
    文章分析了定时器原理与定时器中断,通过实验详细阐述了定时器的使用,尤其是深入分析了各个细节问题。
  • 51单片机定时器工作方式1、2原理详解

    万次阅读 多人点赞 2020-05-30 20:02:24
    51单片机方式1、2的详解:写在前面知识填充方式一工作原理初值的设定代码实现方式二工作原理初值的设定代码实现 写在前面 1.本篇博文旨在帮助那些像我一样刚入门51单片机,如果你对定时器有一定了解,但是其中的的...
  • 《STM32中文参考手册V10》-第14章通用定时器   STM32的定时器 STM32F103ZET6一共有8个定时器,其中分别为: 高级定时器(TIM1、TIM8);通用定时器(TIM2、TIM3、TIM4、TIM5);基本定时器(TIM6、TIM7)。 ...
  • STM32通用定时器基本概述STM32F10x系列的定时器分类STM32F10x系列的定时器数量通用定时器功能特点描述计数模式工作原理 STM32F10x系列的定时器分类 STM32F10x系列的定时器数量 STM32F10x系列总共最多有8个定时器,...
  • 定时器初始化步骤

    千次阅读 2014-12-09 16:53:51
    1.定时器时钟频率 2.设置定时器计数值 3.设置中断处理函数
  • JMeter定时器

    2020-03-31 11:06:50
    JMeter计时器固定定时器统一随机定时器准确的吞吐量定时器常数吞吐量定时器JSR223定时器同步定时器泊松随机定时器高斯随机定时器BeanShell定时器 计时器可模拟延期和暂停。 固定定时器 统一随机定时器 准确的吞吐...
  • 定时器方式0工作原理

    2020-07-30 23:32:27
    定时器工作方式0工作原理Flash动画,形象、直观,方便理解。
  • S7-300的定时器原理和用法

    千次阅读 2014-08-25 17:15:44
    s7-300有5个定时器,分别为SP(脉冲定时器),SE(kuo)
  • C#中定时器的区别,单线程,多线程

    千次阅读 2018-03-27 15:23:08
    原文链接
  • 1、定时器节点设置的每天9:30执行 2、周六整天服务器关停 3、周天下午服务器启动,定时器节点默认执行了一次 问:为什么在系统启动时,未在定时器执行时间,定时器节点也会执行,此处的逻辑是什么! 求解,谢谢...
  • js关闭定时器和重新打开定时器

    千次阅读 2018-08-29 14:42:57
    首先声明一个函数 function fn() { alert(2) } 然后 let t = setInterval(fn, 500) 取消定时器 clearInterval(t) ...重新打开定时器不能重新 let t = setInterval(fn, 500) ;...不然会打开两个定时器...
  • stm32定时器的配置步骤

    千次阅读 2019-04-10 19:02:46
    基本定时器结构功能简单,类似于51单片机中的定时器,通用定时器在基本定时器基础上扩展而来,增加了输入捕获和输出比较等功能,高级定时器在通用定时器的基础上扩展而来,增加了可编程死区互补输出、重复计数器、带...
  • 详细描述了NRF51822蓝牙芯片的定时器使用方法,及更深入的原理机制。
  • 快速选择合适的SIMATIC定时器

    千次阅读 2015-10-21 16:39:24
    快速选择合适的SIMATIC定时器 脉冲S5定时器 S_ PULSE(SP) 扩展脉冲S5定时器S_PEXT(SE) 接通延时S5定时器S_ODT(SD) 保持型接通延时定时器S_ODTS(SS) 断电延时S5定时器S_OFFDT(SF)
1 2 3 4 5 ... 20
收藏数 262,147
精华内容 104,858
关键字:

定时器原理