优化Linux内核定时器功能

文章目录

  • 1.内核定时器
  • 2.内核定时器数据结构
  • 3.内核定时器API
  • 3.1 timer_setup()
  • 3.2 add_timer()
  • 3.3 mod_timer()
  • 3.4 del_timer()
  • 3.5 del_timer_sync
  • 4.相关概念介绍
  • 4.1 节拍率
  • 4.2 节拍数
  • 5. 实例
  • 1.内核定时器

      在Linux 内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序等。
      Linux内核定时器就是timer_list。硬件定时器提供时钟源,时钟源的频率可以设置,设置好以后就周期性的产生定时中断,系统使用定时中断来计时。

    2.内核定时器数据结构

    	struct timer_list {
        struct list_head entry, /*定时器列表*/
        unsigned long expires, /*定时器到期时间,单位为节拍数*/
        void (*function) (unsigned long), /*定时器处理函数*/
        unsigned long data,/*作为参数被传入定时器处理函数*/
        struct timer_base_s *base,
        ...
      };
    

    expires 字段表示期望定时器执行的 jiffies 值,到达该 jiffies 值时,将调用 function 函数,并传递 data 作为参数

    3.内核定时器API

    作用 API
    初始化定时器 void timer_setup(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags)
    注册定时器 void add_timer(struct timer_list *timer)
    修改或再次启动定时器 int mod_timer(struct timer_list *timer, unsigned long expires)
    删除定时器 int del_timer(struct timer_list * timer)
    等待删除定时器 int del_timer_sync(struct timer_list *timer)

    3.1 timer_setup()

    作用:
    	timer_setup函数负责初始化timer_list类型变量,当我们定义了一个timer_list变量以后一定要先用timer_setup初始化一下。
    参数:
    	timer:要初始化定时器。
    	func:定时器的回调函数,此函数的形参是当前定时器的变量。
    	flags: 标志位,直接给0就行。
    返回值:
    	无
    

    3.2 add_timer()

    作用:
    	add_timer函数用于向Linux内核注册定时器,使用add_timer函数向内核注册定时器以后,定时器就会开始运行。
    参数:
    	timer:要注册的定时器。
    返回值:
    	无
    

    3.3 mod_timer()

    作用:
    	mod_timer函数用于修改定时值,如果定时器还没有激活的话,mod_timer函数会激活定时器。
    参数:
    	timer:要修改超时时间(定时值)的定时器。
    	expires:修改后的超时时间。
    返回值:
    	0,调用mod_timer函数前定时器未被激活;
    	1,调用mod_timer函数前定时器已被激活。
    

    3.4 del_timer()

    作用:
    	del_timer函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用del_timer函数删除定时器之前要先等待其他处理器的定时处理器函数退出。
    参数:
    	timer:要删除的定时器。
    返回值:
    	0,定时器还没被激活;
    	1,定时器已经激活。
    

    3.5 del_timer_sync

    作用:
    	del_timer_sync函数是del_timer函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync不能使用在中断上下文中
    参数:
    	timer:要删除的定时器。
    返回值:
    	0,定时器还没被激活;
    	1,定时器已经激活。
    

    4.相关概念介绍

    4.1 节拍率

      内核中有一个宏HZ,表示一秒所对应的节拍数,可以通过这个宏来把时间转换成节拍数,1秒的表示如下:

    mytimer.expires = jiffies+HZ;
    

    HZ的值也可以通过make menuconfig自己设置:

      但是为什么系统节拍率有这么多呢,为什么默认的节拍率这么小?为什么不选择大一点的呢?这里就引出了一个问题:高节拍率和低节拍率的优缺点。

    高节拍优点:
      高节拍率会提高系统时间精度,如果采用100Hz的节拍率,时间精度就是10ms,采用1000Hz的话时间精度就是1ms,精度提高了10倍。

  • 提高内核定时器定时的准确度;
  • 系统时间测量更精细;
  • 系统调用poll()和select()能更高的精度运行;
  • 提高进程抢占的准确度。
  • 高节拍缺点:
      高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担,1000Hz和100Hz的系统节拍率相比,系统要花费10倍的“精力”去处理中断。

  • 中断频率增高,系统负担增加;
  • 中断处理程序占用处理器时间增多;
  • 频繁打断处理器高速缓存。
  • 4.2 节拍数

      jiffies:全局变量,用来记录自系统启动以来产生的节拍总数。启动时内核将该变量初始化为0,jiffies定义在文件include/linux/jiffies.h中。
      前面说了HZ表示每秒的节拍数,jiffies表示系统运行的jiffies节拍数,所以jiffies/HZ就是系统运行时间,单位为秒。
      不管是32位还是64位的jiffies,都有溢出的风险,溢出以后会重新从0开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。假如HZ为最大值1000的时候,32位的jiffies只需要49.7天就发生了绕回,对于64位的jiffies来说大概需要5.8亿年才能绕回,因此jiffies_64的绕回忽略不计。(处理32位jiffies的绕回显得尤为重要,内核有专门的处理函数,此文不再深究。)

    5. 实例

    源文件

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/timer.h>
    
    struct timer_list timer;
    
    void timer_interrupt(struct timer_list* timer_01)
    {
        printk("%s: %lu, %s\n", __func__, jiffies, "hello");
        //到时后再次调用
        mod_timer(&timer, jiffies+HZ);
    }
    static int __init mycdev_init(void)
    {
        printk(KERN_EMERG "%s : %s : %d - ok.\n", __FILE__, __func__, __LINE__);
        printk(KERN_EMERG "hello");
        //初始化定时器
        timer.expires = jiffies + HZ;//定时1s
        timer_setup(&timer, timer_interrupt, 0);
        //注册定时器
        add_timer(&timer);
        return 0;
    }
    
    static void __exit mycdev_exit(void)
    {
        //删除定时器
        del_timer(&timer);
    }
    
    module_init(mycdev_init);
    module_exit(mycdev_exit);
    MODULE_LICENSE("GPL");
    
    

    Makefile

    modname ?= 
    
    arch ?= x86
    ifeq ($(arch),x86)
    	KERNELDIR := /lib/modules/$(shell uname -r)/build
    	CROSS_COMPILE := 
    else
    	KERNELDIR := /home/linux/linux-stm32mp-5.10.61-stm32mp-r2-r0/linux-5.10.61/
    	CROSS_COMPILE := arm-linux-gnueabihf-
    endif
    
    CURRENTDIR := $(shell pwd)
    
    CC := $(CROSS_COMPILE)gcc
    all:
    	make -C $(KERNELDIR) M=$(CURRENTDIR) modules
    	#$(CC) test.c -o test
    install:
    	@cp *.ko  ~/nfs/rootfs/ 
    	#@cp test ~/nfs/rootfs
    
    help:
    	@echo "make arch=arm|x86 modname=modules drivers source file name"
    
    clean:
    	make -C $(KERNELDIR) M=$(CURRENTDIR) clean
    	#rm test
     
    obj-m := $(modname).o
    

    使用dmesg命令查看

    物联沃分享整理
    物联沃-IOTWORD物联网 » 优化Linux内核定时器功能

    发表评论