首页 > 其他分享 >18_内核定时器

18_内核定时器

时间:2024-04-30 15:33:56浏览次数:20  
标签:定时器 18 timer 内核 test gpio printk include

内核定时器

一.Linux内核定时器概念

不同于单片机定时器,Linux内核定时器是一种基于未来时间点的计时方式,以当前时刻为启动的时间点,以未来的某一时刻为终止点。比如,现在是10点5分,我要定时5分钟,那么定时就是10点5分+5分钟=10点10分。这个和咱们的手机闹钟很类似。比如你要定一个第二天早晨的8点的闹钟,就是当前时间定时到第二天早晨8点。

需要注意的是,内核定时器定时精度不高,不能作为高精度定时器使用。并且内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

二.Linux内核定时器基础知识

Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中,定义如下:

struct timer_list {
    struct list_head entry;
    unsigned long expires; /* 定时器超时时间,不是时长,单位是节拍数 */
    struct tvec_base *base;
    void (*function)(unsigned long); /* 定时处理函数 */
    unsigned long data; /* 要传递给 function 函数的参数 */
    int slack;
};

在这个结构体中,有几个参数我们需要重点关注一下。

一个是expires到期时间,单位是节拍数。等于定时的当前的时钟节拍计数(存储在系统的全局变量jiffies)+定时时长对应的时钟节拍数量。

那么我怎么把时间转换成节拍数量呢?

示例:从现在开始定时1秒:

内核中有一个宏HZ,表示一秒对应的时钟节拍数,那么我们就可以通过这个宏来把时间转换成节拍数。

所以,定时1秒就是:

expires = jiffies + 1*HZ

HZ的值我们是可以设置的,也就是说一秒对应的时钟拍数我们是可以设置的,Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。打开文件 include/asm-generic/param.h,有如下内容:

# undef HZ
# define HZ CONFIG_HZ
# define USER_HZ 100

宏HZ就是CONFIG_HZ,因此HZ=100,表示一秒的节拍数是100,们在编译 Linux 内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面。

img

通过上图我们可以发现可选的系统节拍率为 100Hz、 200Hz、 250Hz、 300Hz、 500Hz 和1000Hz。默认是100Hz。

第二个我们需要关心的参数是function超时处理函数,这个并不是硬件中断服务程序。原型:void function(unsigned long data);

第三个参数是data 传递给超时处理函数的参数,可以把一个变量的地址转换成unsigned long 。

三.Linux内核定时器相关操作函数

头文件

#include <linux/timer.h>

1.时间转换函数

<1>ms转换为时钟节拍函数

unsigned long msecs_to_jiffies(const unsigned int m);

<2>us转换为时钟节拍函数

unsigned long usecs_to_jiffies(const unsigned int u);

举例:

​ <1>定时10ms

​ 计算:jiffies +msecs_to_jiffies(10)

​ <2>定时10us

​ 计算:jiffies +usecs_to_jiffies(10)

<3>宏DEFINE_TIMER

原型:

DEFINE_TIMER(_name, _function, _expires, _data);

作用:静态定义结构体变量并且初始化function, expires, data 成员。

参数:

_name 变量名;

_function 超时处理函数 ;

void (*function)(unsigned long);

_expires到点时间,一般在启动定时前需要重新初始化。

_data 传递给超时处理函数的参数

<4>add_timer 函数

原型:

void add_timer(struct timer_list *timer);

作用:add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行

参数:

timer:要注册的定时器。

<5>del_timer 函数

原型:

int del_timer(struct timer_list * timer);

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

timer:要删除的定时器。

返回值: 0,定时器还没被激活; 1,定时器已经激活。

<6>mod_timer 函数

函数原型:

int mod_timer(struct timer_list *timer, unsigned long expires);

作用:mod_timer 函数用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!

参数:

timer:要修改超时时间(定时值)的定时器。

expires:修改后的超时时间节拍数。

返回值:0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活

内核定时器示例

helloworld.c

#include<linux/init.h> //包含宏定义的头文件
#include<linux/module.h> //包含初始化加载模块的头文件

#include <linux/timer.h> //内核定时器相关头文件

void timer_function(unsigned long data); //声明超时处理函数

DEFINE_TIMER(test_timer, timer_function, 0, 0); //静态定义内核定时器并且初始化

void timer_function(unsigned long data) //超时处理函数
{
    printk("This is timer_function\n");
    mod_timer(&test_timer, jiffies + 1*HZ); //修改定时值,如果定时器还没有激活的话,mod_timer 函数会激活定时器!
}



static int hello_init(void)
{
    printk("hello world\n");
    test_timer.expires = jiffies + 1*HZ; //把时间转换成节拍数 定时1秒
    add_timer(&test_timer); //注册定时器
    return 0;
}

static void hello_exit(void)
{
    printk("byby\n");
    del_timer(&test_timer);
}

module_init(hello_init); //驱动模块的入口
module_exit(hello_exit); //驱动模块的出口

MODULE_LICENSE("GPL"); //声明模块拥有开源许可证

Makefile

obj-m +=helloworld.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga 
PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

运行结果

image-20240427192938913

按键定时器消抖实验

driver.c

#include <linux/init.h>            //包含宏定义的头文件
#include <linux/module.h>          //包含初始化加载模块的头文件
#include <linux/platform_device.h> //平台设备所需要的头文件
#include <linux/of.h>              //of函数
#include <linux/of_address.h>      //of_iomap函数
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>

void timer_function(unsigned long data); //声明超时处理函数

struct device_node *test_device_node; // 节点
int gpio_nu;                          // GPIO 编号
int irq;                              // GPIO 对应的中断号

DEFINE_TIMER(test_timer, timer_function, 0, 0); //静态定义内核定时器结构体并且初始化

void timer_function(unsigned long data) //超时处理函数
{
    int value = 0;
    printk("This is timer_function\n");
    value =  __gpio_get_value(gpio_nu); //获取按键gpio值
    if(value == 0) //当按钮被按下,消抖20ms后
    {
        printk("gpio is 0\n");
    }else if(value == 1)
    {
        printk("gpio is 1\n");
    }
}

irqreturn_t test_key(int irq, void *args) // 中断处理函数
{
    printk("test_key is ok\n");
    test_timer.expires = jiffies + msecs_to_jiffies(20); //将20ms的时间转换成节拍数,并定时
    add_timer(&test_timer); //注册定时器
    return IRQ_RETVAL(IRQ_HANDLED); // 正常处理
}

int beep_probe(struct platform_device *pdev)
{
    int ret = 0;

    printk("beep_probe\n");
    test_device_node = of_find_node_by_path("/test_key"); // 查找根节点下的test_key节点
    if (test_device_node == NULL)
    {
        printk("of_find_node_by_path is error\n");
        return -1;
    }
    printk("test_device_node name is %s\n", test_device_node->name);

    gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0); // 获取GPIO标号
    if (gpio_nu < 0)
    {
        printk("of_get_named_gpio is error\n");
        return -1;
    }
    printk("of_get_named_gpio is ok\n");
    ret =  gpio_request(gpio_nu, "key0"); //申请一个 GPIO 管脚
    if(ret != 0)
    {
        printk("gpio_request is error\n");
        return -1;
    }
    printk("gpio_request is ok\n");
    ret = gpio_direction_input(gpio_nu); // 设置GPIO为输入
    if (ret < 0)
    {
        printk("gpio_direction_input is error\n");
        return -1;
    }
    printk("gpio_direction_input is ok\n");

    // irq = gpio_to_irq(gpio_nu); // 获取 gpio 对应的中断号
    irq = irq_of_parse_and_map(test_device_node, 0); //从 interupts 属性中提取到对应的设备号
    if (irq < 0)
    {
        printk("gpio_to_irq is error\n");
        return -1;
    }
    printk("irq is %d\n", irq);

    ret = request_irq(irq, test_key, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "test_key", NULL); //申请中断, 双边沿触发
    if (ret < 0)
    {
        printk("request_irq is error\n");
        return -1;
    }
    printk("request_irq is ok\n");
    return 0;
}

int beep_remove(struct platform_device *platform_device)
{
    printk("beep_remove\n");
    return 0;
}

struct platform_device_id beep_id_table = {
    .name = "123"};

struct of_device_id of_match_table_test[] = { // 与设备树的 compatible 匹配
    {
        .compatible = "keys"},
    {

    }};

/* platform 驱动结构体 */
struct platform_driver beep_platform_driver = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "beep_test",                   // 匹配优先级3
        .of_match_table = of_match_table_test, // 中的.compatible匹配优先级1
    },
    .id_table = &beep_id_table // 中的.name匹配优先级2
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("hello world\n");
    ret = platform_driver_register(&beep_platform_driver); // 注册平台驱动
    if (ret < 0)
    {
        printk("platform_driver_register is error\n");
        return ret;
    }
    return 0;
}

static void beep_driver_exit(void)
{
    printk("byby\n");
    gpio_free(gpio_nu); //释放GPIO
    free_irq(irq, NULL);                               // 释放中断
    platform_driver_unregister(&beep_platform_driver); // 卸载 platform 驱动
    del_timer(&test_timer); //删除定时器
}

module_init(beep_driver_init); // 驱动模块的入口
module_exit(beep_driver_exit); // 驱动模块的出口

MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证

Makefile

obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga 
PWD?=$(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

运行结果

image-20240428005244467

标签:定时器,18,timer,内核,test,gpio,printk,include
From: https://www.cnblogs.com/mzx233/p/18168110

相关文章

  • Vue3+Ts i18n实现国际化
    1、下载依赖npminstallvue-i18n@nex2、在src目录下创建文件夹创建文件index.ts、zh/index.ts、en/index.ts //index.tsimport{createI18n}from'vue-i18n'importzhfrom'./zh/index'importenfrom'./en/index'constmessages={en,zh,}......
  • stm32F07 HAL 库 通过定时器方式实现呼吸灯 自定义呼吸灯函数 (以参数方式设置io
    效果: 1、通过Stm32CubMX开启定时器、设置对应的io口,然后生成工程STM32CubeMX|STM32HAL库方式的微秒延时函数  2、自定义呼吸灯函数代码://呼吸灯函数//GPIO_TypeDef*GPIOx:GPIO组(A-G)//uint16_tGPIO_Pin:IO口(GPIO_Pin_0--GPIO_Pin_16)//......
  • p1182
    #include<stdio.h>#include<stdlib.h>#include<math.h>#include<string.h>#defineN100intF(intn,inta[N]){inti,j,p,q,m,t;intb[N]={0};if(n==0){a[0]=7;}elseif(n==1){a[0]=1;a[......
  • Linux内核源码-存储驱动之 QSPI Flash
    传输方式DIO/QIO/DOUT/QPIQPI模式(QuadPeripheralInterface),所有阶段都通过4线传输。与之相对的是SPI。SPI模式:纯种SPI(MISO/MOSI两个数据线)DOUT全称DualI/O,命令字和地址字均为单线,仅在数据阶段为双线。QOUT全称QuadI/O,命令字和地址字均为单线,仅在数据阶段为双线......
  • mORMot 1.18 第10章 连接表
    mORMot1.18第10章连接表如果数据库包含连接表,那么数据库将变得更加有用。假设读者已经知道何时以及为何需要单独的表,以及为何要将它们连接起来——许多关于数据库设计的书籍都详细介绍了这一点。我们不会在这里重复这些信息,甚至不会重复其中的任何子集。我们要介绍的是如何做......
  • mORMot 1.18 第08章 Delphi中的服务器端JavaScript
    mORMot1.18第8章Delphi中的服务器端JavaScript在mORMot框架中,对JavaScript脚本的支持被称为MonkeyOnRails(版权归PavelMashlyakovsky所有,邮箱:[email protected]),它借助了Mozilla基金会的SpiderMonkey类。mORMot允许程序员编写功能强大的应用程序,但如果客户希望自定义应用......
  • Manthan, Codefest 18 (rated, Div. 1 + Div. 2) D. Valid BFS?
    题意:给一个树和一个bfs序,并保证从节点1出发,判bfs序是否合法。思路:双指针,在bfs序上从左往右遍历。慢指针指向当前节点u,快指针指向当前节点应该访问的节点的位置。然后设两个集合,一个集合存储在当前节点上应该访问的节点,另一个存储在当前节点上实际访问的节点,然后遍历即可。总结:......
  • 升级next@13 react@18 chakra-ui@2
    一、升级步骤1、本地node建议升级到v20(next@13要求node@18,react@18、react-dom@18、chakra-ui@2)2、支持渐进式升级next13,升级的项目需按next官方添加环境变量NEXT_PUBLIC_NEXT13,请devops帮忙Dockerfile构建时添加.env文件到pod中Openimage-20240111-081926.png3......
  • Linux内核之SPI协议
    SPI(SerialPeripheralInterface,串行外设接口)是一种同步串行的行业标准,但是并没有像I2C那样有标准文档,它还有主从、可片选的特性。图源自SerialPeripheralInterface-wikipedia时序图放个经典老图,来源未知。相位和极性决定了采样点,主从采样点一致时数据正确,不一致时会导致......
  • mORMot 1.18 第07章 简单的读写操作
    mORMot1.18第七章简单的读写操作本章描述了典型的数据读写操作。首先,我们将注意力集中在数据上,而不是函数。读取操作返回一个TID,它是一个32位或64位整数(取决于你的内存模型),反映了表的信息。TID在表中的每一行都是唯一的。ORM的新手可能会感到惊讶,但通常你不需要创建SQL查询......