10_linux内核定时器实验

10_linux内核定时器实验一、linux时间管理和内核定时器简介1、内核时间管理简介1)宏HZ​ 硬件定时器产生的周期性中断,中断频率就是系统频率(拍率)。系统拍率可以设置,单位是HZ,可在编译内核时通过图形化界面设置,设置路径如下:KernelFeatures->Timerfrequency([=y])​ 配置完以后,可在内核源码根目录下的.config文件找到CONFIG_HZ的值为所设置的系统频率。而文件include/asm-generic/param.h中的宏:#defineHZ

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

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

1、内核时间管理简介

1)宏HZ

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

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

#define HZ CONFIG_HZ

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

2)jiffies

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

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 一样,多了判断等于
	判断一段代码执行时间有没有超时:
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 中:

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 类型变量,函数原型:

void init_timer(struct timer_list *timer)

timer:要初始化的定时器。

② add_timer函数

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

void add_timer(struct timer_list *timer)

timer:要注册的定时器。

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

③ del_timer函数

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

int del_timer(struct timer_list * timer)

timer:要删除的定时器。

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

④ del_timer_sync函数

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

int del_timer_sync(struct timer_list *timer)

timer:要删除的定时器。

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

⑤ mod_timer函数

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

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 亮灭,在根节点下创建设备节点:

gpioled { 
   
		compatible = "gpioled_test";
		status = "okay";
		pinctrl-names = "default";
		pinctrl-0 = <&gpioled>;
		gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
	};

2)添加pinctrl节点

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

gpioled: ledgrp { 
   
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO03__GPIO1_IO03   0x10b0	
			>;
		};

2、添加设备结构体

/* 设备结构体 */
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、编写加载和卸载注册函数

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

module_init(timer_init);
module_exit(timer_exit);

入口函数:

/* 入口函数 */
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;
}

出口函数:

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

/* 出口函数 */
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 函数里。

/* 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状态切换函数

/* 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、编写定时器初始化函数

/* 定时器初始化函数 */
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 内核的定时器启动后只会运行一次,如果要连续定时,需要在回调函数中重新启动定时器。

/* 定时器回调函数 */
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、编写设备的具体操作函数

/* 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:库函数
​ 添加以下头文件:

#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 是必须的,缺少的话会报错,在文件最末端添加以下代码:

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lzk");

三、编写测试应用程序

#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.net/160362.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • OpenCV-Python实战(17)——人脸识别详解

    OpenCV-Python实战(17)——人脸识别详解随着计算机视觉、机器学习和深度学习的发展,人脸识别已经成为一个热门话题。人脸识别具有广泛的应用前景,包括犯罪预防、智能监视以及社交网络。在本文中,我们介绍OpenCV提供的与人脸识别相关的函数,同时还将探索一些用于人脸识别的深度学习方法,这些方法可以轻松集成到计算机视觉项目中以实现高精度的人脸识别。

    2022年5月18日
    33
  • idea 2021.7 激活码(JetBrains全家桶)

    (idea 2021.7 激活码)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.htmlMLZPB5EL5Q-eyJsaWN…

    2022年3月21日
    80
  • ios事件-触摸事件3(UIButton 和 pointInSide()、hitTest()、touchesBegan()、touchesMoved()、touchesEnded()的关系)

    ios事件-触摸事件3(UIButton 和 pointInSide()、hitTest()、touchesBegan()、touchesMoved()、touchesEnded()的关系)ios事件-触摸事件3(UIButton和pointInSide()、hitTest()、touchesBegan()、touchesMoved()、touchesEnded()、touchesCancelled()的关系)先看效果图本文中,凡是看到xxx(),即表示xxx是一个函数或者方法!!!事件分为事件传递和事件响应,其中,事件响应又称事件处理。具体代码ButtonVC的代码…

    2022年7月25日
    15
  • redis的问题_redis高级数据类型

    redis的问题_redis高级数据类型备注:针对基本问题做一些基本的总结,不是详细解答!1.Redis在项目中的主要作用是是什么?怎么用的?(应用场景)2.Redis支持的数据类型(必考)3.zset跳表的数据结构(必考)4.Redis的数据过期策略(必考)5.Redis的LRU过期策略的具体实现6.如何解决Redis缓存雪崩,缓存穿透问题7.Redis的持久化机制(必考)8.Redis的管道pipel…

    2022年8月20日
    8
  • 一鱼三吃,微信云同步拟收费的醉翁之意「建议收藏」

    一鱼三吃,微信云同步拟收费的醉翁之意「建议收藏」文|熔财经作者|宋文远你能因追综艺接受开通腾讯视频年卡会员,也会为了听高码率音乐成为网易云黑胶会员,但你能接受为了能云备份微信聊天记录,每年少吃十几斤的猪肉吗?据知情人透露,微信聊天记录将为付费用户提供云存储服务,安卓端每年130元,iOS端每年180元。作为国民级社交应用,微信的一举一动都备受关注。最近,聊天纪录云同步拟收费的消息一石激起千层浪,但与往常教张小龙如何做微信不同的是,忍无可忍的网友们不禁发出灵魂质问:“聊天记录备个份也要收钱,还能这样玩的吗?”动则十GB,多则上百..

    2022年5月14日
    68
  • Sqlserver2005日志文件太大,使其减小的方法

    Sqlserver2005日志文件太大,使其减小的方法: 运行下面的三行dbName为数据库名: backuplogdbNamewithNO_LOG backup

    2021年12月26日
    36

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号