精华内容
下载资源
问答
  • 中断怎么产生的

    2013-05-26 12:45:19
    中断是在第二次INTA之前INT脚被拉低而产生的,此时没有中断请求。 我设置8259A为边沿触发,我想问一下 在除了 外设本身 电信号问题化,还有什么情况会触发伪中断。 如果是电平触发,在EOI发送前,外设没有...
  • 最开始在串口通信那里第一次看到关于中断的描述,但是一直以来都没搞清楚中断怎么触发的,中断标志位也不太理解。今天学外部中断的时候好像弄明白了一点点,记录一下。 上图是正点原子家的按键部分的电路图,这个...

    最开始在串口通信那里第一次看到关于中断的描述,但是一直以来都没搞清楚中断是怎么触发的,中断标志位也不太理解。今天学外部中断的时候好像弄明白了一点点,记录一下。

    71ec09c540617f9457a8d8616689e467.png

    20029fa723007d5d6c9427bd52673714.png

    上图是正点原子家的按键部分的电路图,这个外部中断实验的内容是通过按键产生输入中断控制LED灯。 可以看到KEY2连接引脚PE2,于是将中断线EXTI2连接到PE2,下面是部分初始化代码:

        KEY_Init();  // 按键端口初始化
        ......
        GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
        EXTI_InitStructure.EXTI_Line=EXTI_Line2;    //KEY2
        EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; 
        EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
        EXTI_InitStructure.EXTI_LineCmd = ENABLE;
        EXTI_Init(&EXTI_InitStructure);  
        //按键初始化KEY_Init()部分代码
        GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_2
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
        GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE2,3,4
    

    从电路图知道,KEY2按下时使引脚接地电位为0,于是初始化时按键将PE2上拉,在KEY2未按下时,PE2置高电位,按下时置低电位。同时,在配置中断时,将触发方式设置为沿下降沿触发,于是一旦按下KEY2则会引起中断,进入EXTI2_IRQHandler():

    void EXTI2_IRQHandler(void)
    {
        delay_ms(10);//消抖
        if(KEY2==0)   //按键KEY2
        {
            LED0=!LED0;
        }        
        EXTI_ClearITPendingBit(EXTI_Line2);  //清除LINE2上的中断标志位  
    }
    

    中断函数里KEY2是宏定义的引脚PE2的电位检测,进入中断后,检测电位为低电位则说明按下了KEY2,LED变化,然后清除标志位,否则会一直中断。

    而在串口中断里面,一旦接收到数据就会发生中断,同时收到数据就会清除标识位(好像是这样,我忘记那个问答是哪个平台看到的了),所以没有清除标志位串口也正常工作。通用定时器中断则是在计数器向上向下溢出时,计数器初始化的时候产生中断,但是这个就需要手动清除标志位了。

    最后我想电位下降了不是又要回到高电位吗,所以我也尝试把沿下降沿设置为沿上升沿触发,发现按键有的时候有用,大多数时候没用,后来意识到,沿着上升沿触发之后引脚电位应该判断是不是高电位,所以中断函数里面需要判断的是KEY2==1,这样就正常了。

    展开全文
  • DM9000 接受中断产生的原因

    千次阅读 2011-06-09 18:51:00
    在DM9000接受数据的时候一般采取的是中断的方式,这样子比较高效但是我们上位机发生数据给我们的dm9000网卡芯片的时候,接受中断怎么产生的呢: 数据包接收功能是 DM9000 芯片实现网络功能的基础,在接收数据时...

    在DM9000接受数据的时候一般采取的是中断的方式,这样子比较高效但是我们上位机发生数据给我们的dm9000网卡芯片的时候,接受中断是怎么产生的呢:

    数据包接收功能是 DM9000 芯片实现网络功能的基础,在接收数据时采用中断方式,即当有数据到来并在 DM9000 内部 CRC 校验通过后会产生一个接收中断,中断发生时可以将 DM9000 所接收到的数据包读出并交由上层协议进行处理。接收到的数据在经过了硬件部分的 CRC 校验之后存放在 RX FIFO 中,在 DM9000中的内部地址 0x0C00-0x3FFF(13K byte)。在每一个接收到的数据包的前面都有一个 4bytes 的头,这四个字节是01h,status,byte_count_low,byte_count_high.

    展开全文
  • 1:驱动怎么访问寄存器.,其中IOP_BASE等于0xB1600000 ,为什么是这个值,正在查 ,同样的方法访问中断寄存器#define INT_BASE 0xB0A00000,   v_pIOPregs = (volatile S3C2410X_IOPORT_REG *)VirtualAlloc(0, ...


     

    1:驱动怎么访问寄存器.,其中IOP_BASE等于0xB1600000  ,为什么是这个值,正在查  ,同样的方法访问中断寄存器#define INT_BASE      0xB0A00000,

     

     v_pIOPregs = (volatile S3C2410X_IOPORT_REG *)VirtualAlloc(0, sizeof(S3C2410X_IOPORT_REG), MEM_RESERVE, PAGE_NOACCESS);
    
    
      if (!VirtualCopy((PVOID)v_pIOPregs, (PVOID)(IOP_BASE), sizeof(S3C2410X_IOPORT_REG), PAGE_PHYSICAL | PAGE_READWRITE | PAGE_NOCACHE)) 
    
    


    2:驱动怎么使用线程 ,创建这个线程后, EINTKey_IntrThread就自动运行了

    	gEINTIntrThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) EINTKey_IntrThread, 0, 0, &IDThread);
    


    3:事件和中断 绑定, , 事件有2种触发,一种通过中断自动触发,一种是通过set手动触发,

     

    #define IRQ_EINT4           32

    //通过硬件中断号申请系统中断:g_EINTIrq是要申请的中断原始偏移,这里值为IRQ_EINT4 ,g_EINTSysIntr为申请的系统中断

        if (!KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &g_EINTIrq, sizeof(UINT32), &g_EINTSysIntr, sizeof(UINT32), NULL))
    	// 创建外部中断中断事件 
    	gWaitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);		
    
    	// 初始化外部按键中断: 注册中断事件, 允许外部中断
    	if (!(InterruptInitialize(g_EINTSysIntr, gWaitEvent, 0, 0))) 
    	{
    		RETAILMSG(1, (TEXT("ERROR: EINTKey: InterruptInitialize failed.\r\n")));
    		CloseHandle(gWaitEvent);
    		return 0;
    	}

    //怎么判断是否按键按下:在while循环中等待 事件发生了,wait函数就会返回,继续执行

    	while (1) 
    	{
    		ret = WaitForSingleObject(gWaitEvent, INFINITE);
    		if ((ret == WAIT_OBJECT_0) && (g_bKillIST == FALSE))

    //事件发生了,为了通知另外一个wait函数执行,就要调用调用setEvent函数

    				if (Key_IsPushed())		        /* 外部中断按键确实已按下 */
    				{  
    					SetEvent(gReadKeyEvent[0]);	/* 通知读函数, 外部中断按键按键按下 */		
    					RETAILMSG(1, (TEXT("::: The Key1 Pushed. \r\n")));
    				} 

    和中断绑定的事件发生后,要重新释放

    InterruptDone(g_EINTSysIntr);  

     

     

     

     

    展开全文
  • 如果是 A 任务执行完后再执行 B 任务你会怎么调度呢?如果是几十台机器同时要处理一些任务,你又该如何设计呢?带着这些看似不简单的问题我们开始时间之旅。操作系统的时间系统应用程序部署在操作...

    49b79598e03aab3de5fa6211687105d1.png

    “今天想跟大家一起探讨一个听起来很简单的话题:定时任务机制。

    无非就是一个计时器,到了指定时间就开始跑呗。too young,要是这么简单我还说啥呢,干不就完了。

    那如果是几千上万个定时任务,你的计时器该如何设计呢?如果是 A 任务执行完后再执行 B 任务你会怎么调度呢?

    如果是几十台机器同时要处理一些任务,你又该如何设计呢?带着这些看似不简单的问题我们开始时间之旅。

    操作系统的时间系统

    应用程序部署在操作系统上,定时任务依赖操作系统的时钟。鉴于大部分的服务器都部署在 Linux 上,我们就只讨论 Linux 的时间系统,Windows 服务器别打我。

    大部分 PC 机中有两个时钟源,他们分别叫做 RTC(Real Time Clock,实时时钟) 和 OS(操作系统)时钟。

    RTC

    RTC(Real Time Clock,实时时钟)也叫做 CMOS 时钟,它是 PC 主机板上的一块芯片(或者叫做时钟电路),它靠电池供电,即使系统断电也可以维持日期和时间。

    由于独立于操作系统所以也被称为硬件时钟,它为整个计算机提供一个计时标准,是最原始最底层的时钟数据。

    OS 时钟

    OS 时钟产生于 PC 主板上的定时/计数芯片(8253/8254),由操作系统控制这个芯片的工作,OS 时钟的基本单位就是该芯片的计数周期。

    在开机时操作系统取得 RTC 中的时间数据来初始化 OS 时钟,然后通过计数芯片的向下计数形成了 OS 时钟,所以 OS 时钟并不是本质意义上的时钟,它更应该被称为一个计数器。

    OS 时钟只在开机时才有效,而且完全由操作系统控制,所以也被称为软时钟或系统时钟。

    时钟中断

    Linux 的 OS 时钟的物理产生原因是可编程定时/计数器产生的输出脉冲,这个脉冲送入 CPU,就可以引发一个中断请求信号,我们就把它叫做时钟中断。

    Linux 中用全局变量 jiffies 表示系统自启动以来的时钟滴答数目。每个时钟滴答,时钟中断得到执行。

    时钟中断执行的频率很高:100 次/秒(Linux 设计者将一个时钟滴答(tick)定义为 10ms),时钟中断的主要工作是处理和时间有关的所有信息、决定是否执行调度程序。

    和时间有关的所有信息包括系统时间、进程的时间片、延时、使用 CPU 的时间、各种定时器,进程更新后的时间片为进程调度提供依据,然后在时钟中断返回时决定是否要执行调度程序。

    在单处理器系统中,每个 tick 只发生一次时钟中断。在对应的中断处理程序中完成更新系统时间、统计、定时器、等全部功能。

    而在多处理器系统下,时钟中断实际上是分成两个部分:

    • 全局时钟中断,系统中每个 tick 只发生一次。对应的中断处理程序用于更新系统时间和统计系统负载。
    • 本地时钟中断,系统中每个 tick 在每个 CPU 上发生一次。对应的中断处理程序用于统计对应 CPU 和运行于该CPU上的进程的时间,以及触发对应 CPU 上的定时器。

    于是,在多处理器系统下,每个 tick,每个 CPU 要处理一次本地时钟中断;另外,其中一个 CPU 还要处理一次全局时钟中断。

    时钟中断的应用

    更新系统时间:在 Linux 内核中,全局变量 jiffies_64 用于记录系统启动以来所经历的 tick 数。

    每次进入时钟中断处理程序(多处理器系统下对应的是全局时钟中断)都会更新 jiffies_64 的值,正常情况下,每次总是给 jiffies_64 加 1。

    而时钟中断存在丢失的可能。内核中的某些临界区是不能被中断的,所以进入临界区前需要屏蔽中断。

    当中断屏蔽取消的时候,硬件只能告诉内核是否曾经发生了时钟中断、却不知道已经发生过多少次。

    于是,在极端情况下,中断屏蔽时间可能超过 1 个 tick,从而导致时钟中断丢失。

    如果计算机上的时钟振荡器有很高的精度,Linux 内核可以读振荡器中的计数器,通过比较上一次读的值与当前值,以确定中断是否丢失;如果发现中断丢失,则本次中断处理程序会给 jiffies_64 增加相应的计数。

    但是如果振荡器硬件不允许(不提供计数器、或者计数器不允许读、或者精度不够),内核也没法知道时钟中断是否丢失了。

    内核中的全局变量 xtime 用于记录当前时间(自 1970-01-01 起经历的秒数、本秒中经历的纳秒数)。xtime 的初始值就是内核启动时从 RTC 读出的。

    在时钟中断处理程序更新 jiffies_64 的值后,便更新 xtime 的值。通过比较 jiffies_64 的当前值与上一次的值(上面说到,差值可能大于 1),决定 xtime 应该更新多少。

    系统调用 gettimeofday(对应库函数 time 和 gettimeofday)就是用来读 xtime 变量的,从而让用户程序获取系统时间。

    实现定时器:既然已知每个 tick 是 10ms,用 tick 来做定时任务统计再好不过。无论是内核还是应用系统其实都有大量的定时任务需求,这些定时任务类型不一,但是都是依赖于 tick。

    已知的操作系统实现定时任务的方式有哪些呢?

    ①维护一个带过期时间的任务链表

    简单且行之有效的方式。在一个全局链表中维护一个定时任务链。每次 tick 中断来临,遍历该链表找到 expire 到期的任务。

    如果将任务以 expire 排序,每次只用找到链头的元素即可,时间复杂度为 O(1)。

    这种方式对于早期的 Linux 系统来说没有问题,随着现在的系统复杂度渐渐变化,它无法支撑如今的网络流量暴增时代的需求。

    ②时间轮(Timing-Wheel)算法

    1e0f865cacbb37c404c211d4a4863b33.png

    时间轮很容易理解,上图有 n 个 bucket,每一个 bucket 表示一秒,当前 bucket 表示当前这一秒到来之后要触发的事件。

    每个 bucket 会对应着一个链表,链表中存储的就是当前时刻到来要处理的事件。

    那这里有个问题来了,如果有个定时任务需要在 16 小时后执行,换算成秒就是 57600s,难道我们的时间轮也要这么多个 bucket 吗。几万个对内存也是一种损耗。

    为了减少 bucket 的数量,时间轮算法提供了一个扩展算法,即 Hierarchy 时间轮。

    e540b52ea0fe3980dab777076bd93c83.png

    Hierarchy 很好理解,层级制度。既然一个时间轮可能会导致 bucket 过多,那么为什么不能多弄几个轮子来代替时分秒呢?

    基于时、分、秒各自实现一个 wheel,每个 wheel 维护一个自己的 cursor,在 Hour 数组中,每个 bucket 代表一个小时。

    Minute 数组中每一个 bucket 代表 1 分钟,Second 数组中每个 bucket 代表 1 秒。

    采用分层时间轮,我们只需要 24+60+60=144 个 bucket 就可以表示所有的时间。

    完全模拟到时钟的用法,Second wheel 每转完 60 个 bucket ,要联动 Minute wheel 转动一格,同理 Minite wheel 转动 60 个 bucket 也要联动 Hours wheel 转动一格。

    ③维护一个基于小根堆算法的定时任务

    小根堆的性质是满足除了根节点以外的每个节点都不小于其父节点的堆。基于这种性质从根节点开始遍历每个节点能保证获取到一个最小优先级的队列。

    那么应用到定时器中,每次只用获取当前最小堆的 root 节点看是否到期即可。最小堆的插入时间复杂度为 O(lgn),获取头结点时间复杂度为 O(1)。

    开箱即用的定时器

    单机版定时器

    ①cron/crontab

    cron 是 Linux 中的一个定时任务机制。cron 表示一个在后台运行的守护进程,crontab 是一个设置 cron 的工具,所有的定时任务都写在 crontab 文件中

    cron 调度的是 /etc/crontab 文件中的内容。crontab 的命令构成为时间+动作,其时间有分、时、日、月、周五种。

    这里要注意,最小单位为分钟,默认是不到秒的级别,大家也给出了各种精确到秒的方案,有兴趣的可以搜索一下。

    /etc/crontab 文件中的每一行都代表一项任务,它的格式是:

    minute    hour      day       month      dayofweek      user-name      command
    * minute — 分钟,从 0 到 59 之间的任何整数
    * hour — 小时,从 0 到 23 之间的任何整数
    * day — 日期,从 1 到 31 之间的任何整数(如果指定了月份,必须是该月份的有效日期)
    * month — 月份,从 1 到 12 之间的任何整数(或使用月份的英文简写如 jan、feb 等等)
    * dayofweek — 星期,从 0 到 7 之间的任何整数,这里的 0 或 7 代表星期日(或使用星期的英文简写如 sun、mon 等等)
    * user-name - 用户,脚本以什么用户执行
    * command — 要执行的命令(命令可以是 ls /proc >> /tmp/proc 之类的命令,也可以是执行你自行编写的脚本的命令。)
    

    ②JDK 提供的定时器:Timer

    Timer 的思路很简单,基于最小堆的方案创建一个 TaskQueue 来盛 TimerTask。

    Timer 中有一个 TimerThread 线程,该线程是 Timer 中唯一负责任务轮询和任务执行的线程。

    这就意味着如果一个任务耗时很久,久到已经超过了下个任务的开始执行时间,那么就意味下一个任务会延迟执行。

    另外 Timer 线程是不会捕获异常的,如果某个 TimerTask 执行过程中发生了异常而被终止,那么后面的任务将不会被执行。所以要做好异常处理防止出现异常影响任务继续。

    因为有阻塞和异常终止的缺点,JDK 又封装了另一个定时器的实现方式,这次保证不会阻塞。

    因为它是线程池实现方式的一种:ScheduledExecutorService。ScheduledExecutorService 内部将任务封装之后交给了 DelayQueue。

    DelayQueue 是一个依靠 AQS 队列同步器所实现的无界延迟阻塞队列,内部通过 PriorityQueue 来实现,本质还是还是一个堆,所以插入的时间复杂度也是 O(lgn)。

    ③Netty 封装的时间轮:HashedWheelTimer

    上面简要描述了操作系统中的时间轮实现,在著名框架 Netty 中也封装了一个自己的时间轮实现:HashedWheelTimer 类。

    因为 Netty 中需要管理大量的 I/O 超时事件,基于时间轮的方案有助于节省资源。

    Netty 中采用一个轮子的方案,一个格子代表的时间是 100ms,默认有 512 个格子。

    来看看 HashedWheelTimer 的构造函数参数:

    HashedWheelTimer(
        ThreadFactory threadFactory, //类似于Clock中的updater, 负责创建Worker线程.
        long tickDuration,           //时间刻度之间的时长(默认100ms), 通俗的说, 就是多久tick++一次.
        TimeUnit unit,               //tickDuration的单位.
        int ticksPerWheel            //类似于Clock中的wheel的长度(默认512).
    );
    

    另外为了不无休止的增加 bucket,这里采用了轮(round)的概念,一轮所花费的时间:round time=ticksPerWheel*tickDuration。

    如果 bucket 只有 512 个, 而当前休眠时间长于一轮,那么就增加相应的轮次来表示当前休眠时长。

    HashedWheelTimer 中有一些主要的成员:

    • HashedWheelTimer 类本身,主要负责启动 Worker 线程、添加任务等。
    • Worker:内部负责添加任务,累加 tick,执行任务等。
    • HashedWheelTimeout:任务的包装类,链表结构,负责保存 deadline,轮数等。
    • HashedWheelBucket:wheel 数组元素,负责存放 HashedWheelTimeout 链表。

    Worker 线程是 HashedWheelTimer 的核心,主要负责每当已过 tickDuration 时间就累加一次 tick。

    同时也负责执行到期的 timeout 任务和添加 timeout 任务到指定的 wheel 中。

    当添加 Timeout 任务的时候,会根据设置的时间来计算出需要等待的时间长度,根据时间长度进而算出要经过多少次 tick,然后根据 tick 的次数来算出经过多少轮最终得出 task 在 wheel 中的位置。

    对于这种时间轮一般是怎么遍历判断任务到期呢?每个 ticket 到来,都要去遍历每一个 bucket ,以此来判断是否有 bucket 到期。

    所以这种方式就要求 bucket 尽量不要太多,如果太多每次遍历都需要很长的时间。另外就是每次都会遍历,必然会有很多空转,也是一种资源的浪费。

    ④Kafka 中的时间轮:TimingWheel

    Netty 中的时间轮实现采用了单轮+round 的模式,在 Kafka 中采用了多轮的模式。

    上面说过多轮模式下如果按照时分秒来表达,每个轮所需的 bucket 都非常的少,遍历轮的时候就会很快。

    但是多轮也会带来另一个问题就是轮的维护:比如有个定时任务是 1*60*60+50=36050s,这时候就需要分钟和秒轮同时维护这个任务。

    当这个任务继续走,只剩下 59s 的时候,分钟轮就无需在维护它的信息,只剩下秒轮来维护,这里出现了降轮的概念 。

    单机定时机制对比

    以上简单描述了各个实现方案,简单对比可以得出:

    Timer 的实现方案毋庸置疑是最差的。阻塞,异常退出这两条“罪名”无疑让现代程序员无法承受因为出错被老板骂的锅。

    ScheduledExecutorService 使用线程池的方式来异步的执行任务,当任务量巨大的时候,如果设置了优先数量的可执行线程,无疑还是会阻塞任务,好在可执行线程多。

    而 HashedWheelTimer 是面向 bucket 设计,如果采用多轮的方式可以不受任务量限制,任务量非常大的时候,维护数组的成本远远要低于维护堆的成本。

    但是如果是任务量很少的情况,时间轮依旧需要全盘扫描,出现空转的状态,这种空载无疑也是浪费资源的体现。

    所以面向使用场景编程的话:

    • 如果当前待运行的定时任务属于耗时长一点,任务量也不是那么大的时候,可以采用 ScheduledExecutorService 的方式来实现。
    • 如果任务量比较大,任务耗时短,无疑使用 HashedWheelTimer 对内存更加友好。

    定时任务系统

    前面从操作系统时钟源开始,说到时钟中断产生了时钟滴答,所有的定时任务都依赖于此。

    软件层面,通过各种有效的算法在节约资源的前提下通过监听时钟滴答来实现任务。

    还记得开篇提到我们本篇文章的意图是什么吗,要设计一个高效的定时任务系统。

    既然谈到了设计,是不是要先出一版产品需求文档呢。这个真的可以有,我们先提提需求再聊聊方案。

    你要的需求设计

    定时任务系统的核心功能是什么?既然是第一版,我们不要那些花里胡哨,锦上添花的功能,从本质出发。

    5126d2f8795d0072ac4405c2cd85a1e0.png

    我理解应该有三个核心模块:

    • 任务录入:提供录入定时任务的入口,支持最基本的定时任务机制:cron 表达式,自定义执行时间等等方式。
    • 任务调度:通过合适的调度算法从任务库中触发到期的任务以期执行,当然调度系统最好不要直接参数执行,做好自己的事即可。
    • 任务执行:调度系统已经触发了任务,那么可以由专门的执行系统来负责任务执行,执行不会阻塞任务调度,纵然执行有阻塞也是在执行系统中阻塞,保持调度的可用性。

    以上 3 个模块就能满足基本的任务系统需求,接下来聊聊实现方案。

    技术实现方案

    ①录入模块实现

    一般执行定时任务的场景是:每隔多久执行一次操作,这种在业务系统中最常见的就是使用 cron 表达式来代替,所以录入模块要做到可以解析 cron 表达式即可。

    这种录入模式主要是针对后台手动录入任务的场景,对于开发人员来说最优解就是能用代码实现就不去切换鼠标(有同学说能点点鼠标谁还去砌砖)。

    所以还需要提供可执行 jar 包用于业务系统集成,方便开发人员通过编码的方式将任务录入到系统。

    总结一下录入任务的两种途径:

    • 提供业务系统可集成 jar 包,由开发人员编码录入任务。
    • 提供管理后台界面,提供可配置方式录入任务。

    对于业务代码植入式的任务业务服务器启动的时候会通过 jar 包把任务推送过来,对于后台录入的任务那就需要入库保存。

    ②调度模块实现

    在拿到录入模块的定时任务配置信息之后接下来要做的事情:将 cron 表达式变为一个个可执行的时间点。

    比如在 Spring 中就已经提供解析 cron 的功能:CronSequenceGenerator 类可以帮我们执行此操作。

    有了可执行时间点之后要做的事情就是管理它,让它调度起来。上面我们讨论过的各种调度算法此时可以派上用场。

    如果任务密度不是很大,多为固定的定期执行任务,小根堆算法就可以胜任;如果任务密集,很多短期快速执行的任务,可以采用时间轮的方式提高效率。

    另外,比如有个任务是 5 分钟执行一次,那么你一次要解析出来多少个可执行的时间点?一天,一周,一个月?

    这样肯定是有问题的,目前的实现方案是任务首次启动的时候给出第一次执行的时间,每次执行的时候去计算下次任务开始的时间。

    这里有一个点:Java 相关的框架现在实现的方案都是当前任务执行完成之后再计算下次任务开始执行的时间。

    如果任务是 5 分钟一次,当前时间是:10:00,第一个任务完成需要 6 分钟,那么第二个任务开始的时间就是:

    94d6071938a41447f337ed6a612c6f38.png

    我们预期是每隔 5 分钟执行一次,事实上除了第一次是按照预期的准点执行以外,后面都会在绝对时间上有延期。

    到这里我们解决了两个问题:

    • 解析时间表达式为时间点,如何确认周期性任务的下一个可执行时间点。
    • 将可执行时间点送入调度器中,让时间流动起来。

    ③任务执行模块

    任务录入,任务调度我们都完成了,执行模块才是最后的重头戏。这里我们再细化一下,任务录入不能说只是把任务所属的表达式载入系统就完事,要把任务对象化,达到招手即用的状态。

    这里我们把每个任务都封装为一个对象 Job,所有的 Job 都在内存中加载,调度器定义为 Scheduler,把每个可执行时间封装为 Trigger 对象。

    Trigger 用于定义调度任务的事件规则,唯一关联一个 Job 并标识当前 Job 的执行状态。

    f1580156e1dc0c2d70510a209fc36222.png

    上图就是我们的极简版定时任务系统核心功能,怎么样,麻雀虽小,五脏俱全。该有的功能一样不少,不该有的功能一个都没有。

    58f15719709145fe043e96f93a694674.png

    到这里为止我们已经输出了极简版定时任务调度系统的核心设计和实现方案,依据这个方案你可以实现定时任务调度系统的单机版核心功能。

    我们先不提加需求的问题,先来个高可用的问题,上面的方案是将任务加载到一台机器的内存中定时执行,那么如果要实现高可用,多台机器的情况任务如何防止多次执行呢?

    很显然上面的方案肯定是行不通了,下面我们开始扩容。

    高可用

    回答高可用的问题先说目前的思路:单机纯内存抗所有任务。要做高可用必然会大于等于 2 台机器。

    那么两台机器都执行任务必然会重复运行,该用什么方案在多机环境中可以统一管理,统一调度,统一运行任务呢?

    方案一:传统方案-数据库(独占锁)

    任务触发的关键在于 Trigger 触发器,我们只用管住 Trigger 的手让它别乱动 task 就好,基于数据库操作的话,保证任一时刻某个 Trigger 只会被触发一次即可。这里可以使用行级锁来实现。

    某台机器执行到这个 Trigger 的时候向数据库插入一条 Trigger 记录并持有该锁,那么其余机器即使遇到了这个任务也不能执行。

    7fd7aab9c58d573a728b2e922093e572.png

    方案二:分布式组件特性支持(分布式锁)

    一般来说数据库肯定是值得信任的,但是面对实施要求高,任务执行频繁的场景的时候,数据库又是不敢信任的,数据库有一定的并发瓶颈。

    要保证同一时刻的唯一性,除了数据库的锁特性以外,分布式组件肯定也支持,比如 Zookeeper,ETCD 等等。

    可以利用 ZK 的临时节点性质,同一个任务注册一个唯一的节点,哪个机器抢到这个节点谁就来执行任务即可。

    6ad664a3315abaa43cbb1586343d5312.png

    产品加需求

    基础功能我们已经完成,高可用也做到了,上线一段时间,产品觉得的整点幺蛾子啊,不然 KPI 咋整。

    ①新增功能

    基于事件分发的任务机制:可能有一些任务是基于特定的条件触发,这种任务在分布式环境下一般自己实现分布式锁来实现,那么任务系统既然提供分布式特性也可以实现分布式锁的功能。

    所以对于这一类任务完全可以交给任务系统来做,把它当成一次性触发的任务。

    ②新增特性

    任务终止:如果某个任务因为业务需求不再执行,那么是否可以不发布的条件下终止该任务呢?这个时候任务终止的功能就很重要,产品经理暗暗自喜,老板加鸡腿。

    任务依赖:B 任务依赖 A 任务的结果才能执行,所以要提供任务之间的级联操作。

    任务分片:如果我们有 3 台执行任务的机器,有 10 个每 5s 执行一次的定时任务,恰恰每个任务都打到第一台机器。它累如黄牛的时候另外两台还在晒太阳这岂不是资源的浪费嘛。

    为了避免任务集中到某一台机和提高资源利用率,我们需要一种将任务均衡分配到当前所有可执行机器的能力,这就是所谓的分片机制。

    常用的分片算法有如下:

    平均分配算法:

    • 如果有 3 个任务实例,分成 9 片,每个实例对应到的分片就是:1=[1,2,3],2=[4,5,6],3=[7,8,9]。
    • 如果有 3 个任务实例,分成 8 片,每个实例对应到的分片就是:1=[0,1,6],2=[2,3,7],3=[4,5]。
    • 如果有 3 个任务实例,分成 10 片,每个实例对应到的分片就是:1=[0,1,2,9],2=[3,4,5],3=[6,7,8]。

    根据作业名 hash 值决定根据 IP 升序/降序算法:

    • 如果有 3 个任务实例分别为 1,2,3,作业名称对应的 hash 值如果为奇数就按照 IP 升序寻找机器执行,作业名称对应的 hash 值如果为偶数就按照 IP 降序寻找机器执行。这种算法最多要求最多只有两个分片,即只有两台机器参与执行。

    轮询算法:

    • 轮询的原理就很简单,基于可执行机器依次执行。

    任务日志:日志功能肯定不可少,检测任务执行成功与否,任务执行记录、时长,统计任务系统每日任务量等等。

    ③新增容错机制

    容错机制:任务执行失败,可能是任务本身逻辑问题,也可能是外部条件,所以可以设置一些容错机制,给它一次重试的机会。

    故障转移:集群中如果某一台机器发生了故障,它如果还在注册中心注册,那么任务会被该机器执行,很显然如果仅有失败重试策略,那么这个任务永远都不会执行成功。

    首先需要心跳检测机制,检测活动机器是否健康;其次需要在重试失败之后做任务转移操作,防止多次失败仍在同一台机器吊死。

    手动触发:如果万不得已遇到任务没有执行到的情况 ,那么是否要提供手动触发的机制呢?我想产品经理这种人精肯定不想背锅,所以你还是做吧!

    后话

    做完上面的功能之后,产品经理躺在他的折叠床上打着呼噜鼻子不时的还冒几个泡安心的睡着了。

    程序员小哥整苦逼的构思这些功能该如何实现,是给 3 天还是给 3 个月,一场人月神话即将上演。

    目前圈子里比较流行的定时任务系统有 Quartz,XXLJob,Elastic Job 等,实现方式不会脱离上文描述的范围。

    这些都是程序员自己没事捣鼓的实用型系统,有需求就有产出,有方向就有动力。

    工作之余大家也可以自己思考目前在写的东西是否可以抽象为大层次的一个功能,简单说,你是否也能整出个中台来。

    在这个万物皆中台的时代,大家不遗余力的照虎画瓢,虽说可能画出个四不像,起码对于写代码的人,抽象能力是得到了锻炼。

    作者:杨越

    简介:目前就职广州欢聚时代,专注音视频服务端技术,对音视频编解码技术有深入研究。日常主要研究怎么造轮子和维护已经造过的轮子,深耕直播类 APP 多年,对垂直直播玩法和应用有广泛的应用经验,学习技术不局限于技术,欢迎大家一起交流。

    展开全文
  • 2、EINTR错误产生的原因 如果进程在一个慢系统调用(slow system call)中阻塞时,当捕获到某个信号且相应信号处理函数返回时,这个系统调用被中断,调用返回错误,设置errno为EINTR(相应的错误描述为...
  • 你的mega128的串口接收程序写的有问题,和什么模块都无关。串口中断是每收到一个字符就会产生一次的,所以你不要看到中断了 就急着处理,收的东西还没全呢,怎么处理...下面是用CVAVR自动产生的串口中断接收程序,支持
  • 就是硬件产生中断时,向8259a产生中断信号,然后8259a向CPU发出中断信息,并产生相应的中断向量,然后cpu可以从中读取该中断向量,然后调用相应的中断处理程序,这个调用是在中断描述符表中根据根据 中断向量 * 8 的...
  • 注:TF1是定时器T1的溢出中断申请位,产生溢出时,TF1置1 C语言可能是 while(!TF1);//一直在这循环,直到TF1=1 中断就是利用内部中断资源,当发生中断时,自动跳到中断处理程序处,不需要做无谓...
  • 共用中断和共用中断函数的判断

    千次阅读 2016-10-10 09:53:46
    STM32外部中断查询: 15-10线的外部中断共用一个中断函数,怎么在该函数里查询是哪个中断线产生中断呢? 使用 EXTI_GetITStatus()来查询哪根线产生中断。比如EXTI_GetITStatus(EXTI_Line13)就是查询13线是否...
  • ESC键是怎么产生的

    2012-10-11 10:36:19
    ESC键是怎么产生的? ESC键诞生于1960年,当时IBM的程序员Bob Bemer正在解决一个巴别塔难题: 不同生产商的电脑使用不同的代码交流。于是Bemer发明了ESC键,让程序员们能 通过它从一种代码切换到另外 一种。后来...
  • STM32共用中断和共用中断函数的判断

    千次阅读 2016-12-09 21:21:35
    STM32外部中断查询: 15-10线的外部中断共用一个中断函数,怎么在该函数里查询是哪个中断线产生中断呢?使用 EXTI_GetITStatus()来查询哪根线产生中断。 比如EXTI_GetITStatus(EXTI_Line13)就是查询13线是否...
  • 4、实验过程建立工程,设置并初始化串口中断,在运行程序之后,如果串口接收到N(1-63)个字节数据,则产生串口中断,Zynq响应中断,将数据从RXFIFO读出之后写入到DDR3预定的地址中。5、实验平台Microphase ZUS zynq...
  • 另外如果是用TIM2,TIM3,TIM4这四个定时器的中断分别翻转来控制产生占空比,那怎么对齐呢?有点想不通了。。 3.在WHILE(1)的大循环里面用计时器计数,进行电平翻转,这样可以正常输出。但问题是在执行中断时,会...
  • 用8259A的输出作为中断请求信号使8259A产生中断,进入中断后显示一个字符串。中断请求通过8259的IRQ3端输入,中断源要求是8253定时器输出的脉冲,并把8253的OUT0和总线槽IRQ3连接好. 编写程序,要求每次主机响应外部中断...
  • 外部中断

    2020-01-17 22:26:56
    这么多IO口,怎么都可以产生中断请求?答案是映射。这样GPIOA-G的0-15就映射到EXTI0-15 共16个中断线 16*7=112。但是在同一时间只能有一个IO口映射到中断线。 这些中断中断服务函数怎么分配的呢? 从表中...
  • linux的底层中断处理分析

    千次阅读 2012-06-25 15:26:40
    所有的外设中断反应到CPU上的IRQ或FIQ中断,而且不同于普通的8位单片机的是,这么多的外设中断并不是一一对应多个中断向量,而是对应到FIQ或IRQ,需要在IRQ或FIQ中断处理程序里进一步判断是哪个外设中断产生了,另外...
  • linux共享中断的处理

    千次阅读 2011-12-22 17:52:50
    Linux可以让多个设备共享一个中断号,而且共享同一中断中断处理程序形成一个链表,内核对每个中断处理程序都要执行,那么,没有产生中断的设备本 该靠边站的,它的中断处理程序也被执行了?到底是怎么会事?...
  • S3C2440的按键中断程序

    2019-04-21 20:24:05
    按键中断是一种外部中断,即他属于一种异常状态,可以产生中断。那么我们怎么样来分析和编写一个基于S3C2440的按键中断代码呢。 一、第一部我们要初始化号响应的参数,分为三步 1.我们肯定要设置一个中断源,让他...
  • 中断处理

    2014-09-14 21:40:19
    中断也是一种异常。 问1:中断的来源很多,如按键、触摸屏等。当众多中断到来时,先处理... 问2: 怎么产生中断呢? 答:初始化能产生中断的硬件: a、设置中断源,使其能发出中断 b、设置中断控制器 c、全能总开关 c
  • Cortex-M系列:中断的内在机理

    千次阅读 2020-01-21 13:54:48
    在博客[2]Cortex-M系列: 软件中断和硬件中断中,主要写的是要怎么配置中断产生中断,而本篇主要将CPU是如何识别我们的代码并运行中断,同时不破坏程序的上下文的。文中用一个例子解释了惰性压栈的原理。在发生...
  • 上一节讲完了根据中断类型号找中断服务程序的过程,现在着重说明一下更加完整的中断处理过程吧。 本节以8086时代的中断处理过程为例进行说明,主要分两大...对于中断的产生,分为内部和外部,内部就是指令执行产生的
  • STM32 15-10线的外部中断共用一个中断函数,怎么在该函数里查询是哪个中断线产生中断呢? 使用 EXTI_GetITStatus()来查询哪根线产生中断。比如EXTI_GetITStatus(EXTI_Line13)就是查询13线是否产生中断的。 你...
  • 后来经过分析出现问题时的panic的堆栈,借助EJTAG工具,读到这个时候的串口的中断状态位,竟然发现串口并没有真正产生中断。那么,串口本身没有中断,内核怎么又会跑到串口的中断服务函数去执行...
  • 线程的中断标志位

    2021-04-09 11:39:35
    当线程sleep或wait、join时,如果这时设置标志位会产生InterruptedException异常,catch后可以进行处理,标志位也会复位。 锁操作不会被Interrupte()干扰 lockInterruptibly()可以干扰,使其抛出异常,catch异常...
  • STM32外部中断

    2021-01-10 21:00:30
    外部中断/时间控制器包含19个边沿检测器,用于产生中断/时间请求。每个中断线都可以独立地配置它的触发事件(上升沿或下降沿或双边沿) 并能够单独地屏蔽:有一个挂起寄存器维持所有中断请求的状态。EXTI可以检测到...

空空如也

空空如也

1 2 3 4 5 ... 14
收藏数 267
精华内容 106
关键字:

中断怎么产生的