目录

定时器的实现原理

内核启动注册时钟中断

内核时钟中断处理流程

内核定时器时间轮算法

定时器的使用方法

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


定时器的实现原理

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

  • Linux内核的 Hierarchy 时间轮算法
  • Asio C++ Library最小堆定时器实现
  • nginx 使用红黑树结构管理定时器事件

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_WORKif (in_irq())irq_work_tick();
#endifscheduler_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 8typedef 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);*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的周期心跳// testadd_timer(1, do_time1);add_timer(2, do_time2);add_timer(9, do_time9);while(1) pause();return 0;
}

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

Linux·定时器原理与使用相关推荐

  1. 让事件飞——Linux eventfd 原理

    让事件飞--Linux eventfd 原理 让事件飞 --Linux eventfd 原理与实践 原文作者:杨阳 eventfd/timerfd 简介 目前越来越多的应用程序采用事件驱动的方式实现功 ...

  2. Linux定时器:无节拍机制tickless(CONFIG_NO_HZ)

    Linux定时器:无节拍机制tickless(CONFIG_NO_HZ) BAT-Battle 2013-09-01 Tickless 机制是Linux 内核中引入的新定时机制 以前,Linux内核会 ...

  3. linux软中断是什么机制,Linux软中断原理浅析

    Linux软中断原理浅析 Linux软中断原理浅析 Linux中的软中断机制用于中对时间要求最严格以及最重要的中断下半部进行使用.在系统设计过 程中,大家都清楚中断上下文不能处理太多的事情,需要快速的 ...

  4. Linux串口原理与编程

    Linux 串口原理 2. How the Hardware Transfers Bytes     mit这里说得巨清楚,串口相关原理的看这个就行,CPU和MCU串口接收差不多. When the ...

  5. linux上的定时器上的jiffies,linux定时器和Jiffies汇.doc

    linux定时器和Jiffies汇 1.linux HZ Linux核心几个重要跟时间有关的名词或变数,将介绍HZ.tick与jiffies. HZ Linux核心每隔固定周期会发出timer int ...

  6. Linux 操作系统原理 — 内存 — 内存分配算法

    目录 文章目录 目录 前文列表 内存碎片 伙伴(Buddy)分配算法 Slab 算法 虚拟内存的分配 内核态内存分配 vmalloc 函数 kmalloc 用户态内存分配 malloc 申请内存 用户 ...

  7. Linux 操作系统原理 — 内存 — 基于局部性原理实现的内/外存交换技术

    目录 文章目录 目录 前文列表 基于局部性原理实现的内-外存交换技术 局部性原理 Swap 交换分区 前文列表 <Linux 操作系统原理 - 内存 - 物理存储器与虚拟存储器> < ...

  8. Linux 操作系统原理 — 内存 — 页式管理、段式管理与段页式管理

    目录 文章目录 目录 前文列表 页式管理 快表 多级页表 基于页表的虚实地址转换原理 应用 TLB 快表提升虚实地址转换速度 页式虚拟存储器工作的全过程 缺页中断 为什么 Linux 默认页大小是 4 ...

  9. Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术

    目录 文章目录 目录 前文列表 物理地址与虚拟地址 内存空间的组织方式 虚拟地址空间的编址 内核态地址空间 用户态地址空间 内-外存空间的交换与虚拟存储空间之间的映射关系 缺页异常 前文列表 < ...

最新文章

  1. 都是程序员,凭什么他能站在鄙视链的顶端?
  2. Arduino可穿戴教程Linux平台下安装Arduino IDE
  3. Linux内核模块简介
  4. Libnids库-网络入侵检测的基础框架
  5. Bug错误openssl_encrypt()
  6. 服务器设备性能说明,OMC服务器硬件性能和配置说明.doc
  7. Docker 容器 和 虚拟机 的异同
  8. 【spring】【转】Spring 框架的设计理念与设计模式分析
  9. Buffer(缓冲/字节容器)详解
  10. 下班后比你多学2个小时的人,在看这几个公众号
  11. 获取移除指定Url参数(原创)
  12. 在windows下安装node-sass失败,提示\node-sass: Command failed,解决方案
  13. Java Web架构演变
  14. mongodb and ssis
  15. 企查查python爬虫实例
  16. basic4android 开发 使用类库方法
  17. 【项目】08年度科创项目“绘图助手工具箱”项目成果发布
  18. 无水印火山小视频下载教程
  19. Springboot毕业设计毕设作品,心理评测系统 开题报告
  20. 论文 | 知识图谱自动构建 Automatic Knowledge Graph Construction

热门文章

  1. 【Spring5】Spring项目运行时报错:IOException parsing XML document from class path resource [bean.xml]
  2. 基于tp框架的自定义常用函数,包括图片操作、阿里云OSS、文件下载、短信验证码等...
  3. Python实现的进程管理神器——Supervisor
  4. 算法竞赛基础训练题_填空题
  5. Promise用法及使用案例
  6. IPMI之Ipmitool工具
  7. 计算机键盘上范的怎么点击,软键盘怎么关,详细教您电脑怎么关软键盘
  8. TCP/IP(八)之总结TCP/IP四层模型
  9. 全景拍照返回键无保存
  10. 正则表达式-Linux系统的任督二脉