前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >10_linux内核定时器实验

10_linux内核定时器实验

作者头像
全栈程序员站长
发布2022-09-13 10:33:24
2K0
发布2022-09-13 10:33:24
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

一、linux时间管理和内核定时器简介

1、内核时间管理简介

1)宏HZ

​ 硬件定时器产生的周期性中断,中断频率就是系统频率(拍率)。系统拍率可以设置,单位是HZ,可在编译内核时通过图形化界面设置,设置路径如下:Kernel Features -> Timer frequency([=y])

​ 配置完以后,可在内核源码根目录下的 .config 文件找到 CONFIG_HZ 的值为所设置的系统频率。而文件 include/asm-generic/param.h 中的宏:

代码语言:javascript
复制
#define HZ CONFIG_HZ

​ 在编译驱动时经常使用 HZ 宏来设置定时器的参数。

2)jiffies

​ linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动时 jiffies 初始化为 0。jiffies 定义在文件 include/linux/jiffies.h 中:

代码语言:javascript
复制
extern u64 __jiffy_data jiffies_64; 
extern unsigned long volatile __jiffy_data jiffies;

​ 其中 jiffies_64 和 jiffies 是同一个东西,jiffies_64 用于 64 位系统,jiffies 用于 32 位系统。jiffies 是 jiffies_64 的的低32位。jiffies 会有溢出的风险,当 jiffies 溢出后就会从 0 开始极数(绕回)。假如 HZ 为 1000,则 49.7 天就会发生绕回,linux 内核提供了 API 来处理绕回:

函数

描述(unkown 通常为 jiffies,known 通常是需要对比的值。)

time_after(unkown, known)

如果 unkown 超过 known,返回真,否则返回假

time_before(unkown, known)

如果 unkown 没超过 known,返回真,否则返回假

time_after_eq(unkown, known)

和 time_after 一样,多了判断等于

time_before_eq(unkown, known)

和 time_before 一样,多了判断等于

代码语言:javascript
复制
	判断一段代码执行时间有没有超时:
代码语言:javascript
复制
unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点2S */

/************************************* 具体的代码 ************************************/

 /* 判断有没有超时 */
if(time_before(jiffies, timeout)) { 
   
/* 超时发生 */
} else { 
   
/* 超时未发生 */
}

​ linux 内核提供了 jiffies 和 ms、us、ns 之间的转换函数:

函数

描述

int jiffies_to_msecs(const unsigned long j)

将 jiffies 类型的参数 j 分别转换为对应的毫秒

int jiffies_to_usecs(const unsigned long j)

将 jiffies 类型的参数 j 分别转换为对应的微秒

u64 jiffies_to_nsecs(const unsigned long j)

将 jiffies 类型的参数 j 分别转换为对应的纳秒

long msecs_to_jiffies(const unsigned int m)

将毫秒转换为 jiffies 类型

long usecs_to_jiffies(const unsigned int u)

将微秒转换为 jiffies 类型

unsigned long nsecs_to_jiffies(u64 n)

将纳秒转换为 jiffies 类型

2、内核定时器简介

1)timer_list 结构体

​ 使用内核定时器,不需要设置寄存器,内核提供了定时器结构体和 API 函数。内核使用 timer_list 结构体来表示内核定时器,timer_list 定义在 include/linux/timer.h 中:

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

2)定时器API函数

① init_timer函数

​ init_timer 函数用于初始化 timer_list 类型变量,函数原型:

代码语言:javascript
复制
void init_timer(struct timer_list *timer)

timer:要初始化的定时器。

② add_timer函数

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

代码语言:javascript
复制
void add_timer(struct timer_list *timer)

timer:要注册的定时器。

注意:一般重复启动定时器推荐使用 mod_timer。

③ del_timer函数

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

代码语言:javascript
复制
int del_timer(struct timer_list * timer)

timer:要删除的定时器。

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

④ del_timer_sync函数

​ del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中。函数原型如下所示:

代码语言:javascript
复制
int del_timer_sync(struct timer_list *timer)

timer:要删除的定时器。

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

⑤ mod_timer函数

​ mod_timer 函数用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器。函数原型如下:

代码语言:javascript
复制
int mod_timer(struct timer_list *timer, unsigned long expires)

timer:要删除的定时器。

expires:超时时间。

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

3、linux 内核短延时函数

​ Linux内核提供了毫秒、微秒和纳秒延时函数,这三个函数如表:

函数

描述

void ndelay(unsigned long nsecs)

纳秒延时函数

void udelay(unsigned long usecs)

微秒延时函数

void mdelay(unsigned long mseces)

毫秒延时函数

二、内核定时器实验

1、添加设备树节点

1)添加设备节点

​ 实验使用定时器控制 led 亮灭,在根节点下创建设备节点:

代码语言:javascript
复制
gpioled { 
   
		compatible = "gpioled_test";
		status = "okay";
		pinctrl-names = "default";
		pinctrl-0 = <&gpioled>;
		gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
	};

2)添加pinctrl节点

​ 在 iomuxc 节点下的子节点 imx6ul-evk 中添加 pinctrl 节点:

代码语言:javascript
复制
gpioled: ledgrp { 
   
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO03__GPIO1_IO03   0x10b0	
			>;
		};

2、添加设备结构体

代码语言:javascript
复制
/* 设备结构体 */
struct timer_dev { 
   
	dev_t devid;	//设备号
	int major;		//主设备号
	int minor;		//次设备号
	struct cdev cdev;	//字符设备
	struct class *class;	//类
	struct device *device;	//设备
	struct spinlock	lock;	//自旋锁
	int gpioled;		//led gpio编号
	struct device_node *led_nd;	//led节点
	struct timer_list timer;	//定时器
	unsigned long expires;	//定时时间
};

struct timer_dev timer;

3、编写加载和卸载注册函数

​ 加载和卸载注册函数如下:

代码语言:javascript
复制
module_init(timer_init);
module_exit(timer_exit);

入口函数:

代码语言:javascript
复制
/* 入口函数 */
static int __init timer_init(void)
{ 
   
	int ret = 0;
	spin_lock_init(&timer.lock);

	/* 设备号处理 */
	timer.major = 0;
	if(timer.major){ 
   	//指定了主设备号
		timer.devid = MKDEV(timer.major, 0);
		ret = register_chrdev_region(timer.devid, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0){ 
   
			return -EINVAL;
		}
	}else{ 
   		//没有指定设备号
		ret = alloc_chrdev_region(&timer.devid, 0, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0){ 
   
			return -EINVAL;
		}
		timer.major = MAJOR(timer.devid);
		timer.minor = MINOR(timer.devid);
		printk("major is: %d\r\nmainr is: %d\r\n",timer.major, timer.minor);
	}

	/* 注册字符设备 */
	timer.cdev.owner = THIS_MODULE;
	cdev_init(&timer.cdev, &timer_fops);
	ret = cdev_add(&timer.cdev, timer.devid, DEVICE_CNT);
	if(ret < 0){ 
   
		ret = -EINVAL;
		goto fail_cdev;
	}

	/* 自动创建节点 */
	timer.class = NULL;
	timer.device = NULL;
	timer.class = class_create(THIS_MODULE, DEVICE_NAME);
	if(timer.class == NULL){ 
   
		ret = -EINVAL;
		goto fail_create_class;
	}
	timer.device = device_create(timer.class, NULL, timer.devid, NULL, DEVICE_NAME);
	if(timer.device == NULL){ 
   
		ret = -EINVAL;
		goto fail_create_device;
	}

	return 0;

fail_cdev:
	unregister_chrdev_region(timer.devid, DEVICE_CNT);
fail_create_class:
	cdev_del(&timer.cdev);
	unregister_chrdev_region(timer.devid, DEVICE_CNT);
fail_create_device:
	class_destroy(timer.class);
	cdev_del(&timer.cdev);
	unregister_chrdev_region(timer.devid, DEVICE_CNT);

	return ret;
}

出口函数:

​ 如果激活了定时器,在卸载模块时,一定要先删除定时器,否则将无法卸载模块。

代码语言:javascript
复制
/* 出口函数 */
static void __exit timer_exit(void)
{ 
   
	led_switch(LED_OFF);
	gpio_free(timer.gpioled);			//注销led gpio
	del_timer_sync(&timer.timer);		//删除定时器
	device_destroy(timer.class, timer.devid);	//摧毁设备
	class_destroy(timer.class);			//摧毁类
	cdev_del(&timer.cdev);				//删除字符设备
	unregister_chrdev_region(timer.devid, DEVICE_CNT);	//注销设备号
}

4、编写led gpio初始化函数

​ 将 led gpio 函数放在 open 函数里。

代码语言:javascript
复制
/* gpio初始化函数 */
static int gpio_init(void)
{ 
   
	/* 获取设备树节点 */
	timer.led_nd = of_find_node_by_name(NULL, "gpioled");
	if(timer.led_nd == NULL){ 
   
		printk("fail to find led_nd\r\n");
		return -EINVAL;
	}
	/* 获取gpio编号 */
	timer.gpioled = of_get_named_gpio(timer.led_nd, "gpios", 0);
	if(timer.gpioled < 0){ 
   
		printk("fail to find led_nd\r\n");
		return -EINVAL;
	}
	printk("gpio num: %d\r\n",timer.gpioled);
	/* 设置gpio */
	gpio_request(timer.gpioled,"led");
	gpio_direction_output(timer.gpioled, 0);
	printk("led init\r\n");

	return 0;
}

5、编写led状态切换函数

代码语言:javascript
复制
/* LED状态切换函数 */
void led_switch(int led_state)
{ 
   
	if(led_state == LED_ON){ 
   
		gpio_set_value(gpioled.led_gpio, 0);	//使用gpio子系统的API函数
	}else{ 
   
		gpio_set_value(gpioled.led_gpio, 1);
	}
}

6、编写定时器初始化函数

代码语言:javascript
复制
/* 定时器初始化函数 */
void Timer_Init(void)
{ 
   
    unsigned long flags;
    
	init_timer(&timer.timer);					//初始化定时器
	timer.timer.function = timer_function;		 //注册定时回调函数
	timer.timer.data = (unsigned long)&timer;	 //将设备结构体作为回调函数的传入参数
    spin_lock_irqsave(&timer.lock, flags);		 //上锁
	timer.timer.expires = jiffies + 2 * HZ;		 //设置超时时间 2S,给add_timer用
    timer.expires = timer.timer.expires;		 //回调函数中下周期的定时时间
    spin_unlock_irqrestore(&timer.lock, flags);	  //解锁
}

7、编写定时回调函数

​ linux 内核的定时器启动后只会运行一次,如果要连续定时,需要在回调函数中重新启动定时器。

代码语言:javascript
复制
/* 定时器回调函数 */
void timer_function(unsigned long arg)
{ 
   
	unsigned long flags;
	static int led_state = 1;
	struct timer_dev *dev = (struct timer_dev *)arg;
	int time_period = 0;

	led_switch(led_state = !led_state);		//切换led状态
	printk("\r\nled state : %d\r\n", led_state);
	/* 重启定时器 */
	spin_lock_irqsave(&dev->lock, flags);
	time_period = dev->expires;
	spin_unlock_irqrestore(&dev->lock, flags);
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(time_period));	//设置定时时间,启动定时器
}

8、编写设备的具体操作函数

代码语言:javascript
复制
/* open函数 */
static int timer_open(struct inode *inode, struct file *filp)
{ 
   
	int ret = 0;
	filp->private_data = &timer;	//设置私有化数据

	ret = gpio_init();	 //初始化 led gpio
	Timer_Init();		//初始化定时器

	return ret;
}

/* * @description : ioctl函数, * @param – filp : 要打开的设备文件(文件描述符) * @param - cmd : 应用程序发送过来的命令 * @param - arg : 参数 * @return : 0 成功;其他 失败 110 */
static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{ 
   
	struct timer_dev *dev = filp->private_data;
	unsigned long flags,time;


	printk("cmd = %x arg = %ld\r\n",cmd, arg);
	switch(cmd){ 
   
		case TIMER_OPEN:
			add_timer(&dev->timer);		//注册并启动定时器
		break;
		case TIMER_CLOSE:
			del_timer_sync(&dev->timer);		//删除定时器
		break;
		case TIMER_SET:
			spin_lock_irqsave(&dev->lock, flags);		//上锁
			dev->expires = arg;
			spin_unlock_irqrestore(&dev->lock, flags);		//解锁
			mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));	//设置定时时间,启动定时器
		break;
		default:
		break;
	}
	return 0;
}

/* 操作函数集 */
static const struct file_operations timer_fops = { 
   
	.owner = THIS_MODULE,
	.open  = timer_open,
	.unlocked_ioctl = timer_unlocked_ioctl,
};

4、添加头文件

​ 参考 linux 内核的驱动代码时,找到可能用到的头文件,添加进工程。在调用系统调用函数或库函数时,在终端使用 man 命令可查看调用的函数需要包含哪些头文件。 ​ man 命令数字含义:1:标准命令 2:系统调用 3:库函数 ​ 添加以下头文件:

代码语言:javascript
复制
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

#define DEVICE_CNT 1
#define DEVICE_NAME "timer"
#define LED_ON 1
#define LED_OFF 0
#define TIMER_OPEN 0X0F
#define TIMER_CLOSE 0X1F
#define TIMER_SET 0XFF

5、添加 License 和作者信息

​ 驱动的 License 是必须的,缺少的话会报错,在文件最末端添加以下代码:

代码语言:javascript
复制
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lzk");

三、编写测试应用程序

代码语言:javascript
复制
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include "linux/ioctl.h"

#define TIMER_OPEN 0X0F
#define TIMER_CLOSE 0X1F
#define TIMER_SET 0XFF

int main(int argc, char *argv[])
{ 
   
    int fd = 0;
    int ret = 0;
    u_int32_t cmd = 0;
    u_int32_t arg = 0;
    char *filename;
    unsigned char str[100];
    
    if(argc != 2){ 
   
        printf("missing parameter!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0){ 
   
        printf("open file %s failed\r\n", filename);
        return -1;
    }

    while(1){ 
   
        printf("Input CMD:");
        scanf("%d", &cmd);

        if(cmd == 1)			//命令1,打开定时器,按照初始设定闪灯
            cmd = TIMER_OPEN;
        if(cmd == 2)			//命令2,关闭定时器
            cmd = TIMER_CLOSE;
        if(cmd == 3){ 
   			//命令3,设置定时时间
            cmd = TIMER_SET;
            printf("input time:");
            scanf("%d", &arg);
        }
        ioctl(fd,cmd,arg);		//将命令传给内核空间
    }


    ret = close(fd);
    if(ret < 0) //返回值小于0关闭文件失败
    { 
   
        printf("close file %s failed\r\n", filename);
        return -1;
    }

    return 0;
}

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/160362.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、linux时间管理和内核定时器简介
    • 1、内核时间管理简介
      • 1)宏HZ
      • 2)jiffies
    • 2、内核定时器简介
      • 1)timer_list 结构体
      • 2)定时器API函数
    • 3、linux 内核短延时函数
    • 二、内核定时器实验
      • 1、添加设备树节点
        • 1)添加设备节点
        • 2)添加pinctrl节点
      • 2、添加设备结构体
        • 3、编写加载和卸载注册函数
          • 入口函数:
          • 出口函数:
        • 4、编写led gpio初始化函数
          • 5、编写led状态切换函数
            • 6、编写定时器初始化函数
              • 7、编写定时回调函数
                • 8、编写设备的具体操作函数
                  • 4、添加头文件
                    • 5、添加 License 和作者信息
                    • 三、编写测试应用程序
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档