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;
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • Oracle运算符

    Oracle运算符

  • 网络号和主机号的计算

    网络号和主机号的计算网络号和主机号的计算当前使用的IP地址有4个字节(32)组成,即IPV4编码方式。每个IP地址包换两部分:网络号和主机号。当分配给主机号的二进制位越多,则能标识的主机数就越多,相应地能标识的网络数就越少,反之亦然。IP地址分为五类,A类保留给政府机构,B类分配给中等规模的公司,C类分配给任何需要的人,D类用于组播,E类用于实验,各类可容纳的地址数目不同。A、B、C三类IP地址的特征:当将IP…

  • sublime如何实现函数折叠

    sublime如何实现函数折叠

  • 使用C语言实现字符串分割

    使用C语言实现字符串分割之前分享了一篇使用C++(std::string作为容器)进行字符串分割的博文:https://blog.csdn.net/r5014/article/details/82802664 现在又想用C语言做一个字符串分割的函数,大概功能是这样:需要分割的字符串“  thisisacharactor raw.  ”使用”分割分割之后会返回一个char**…

  • python线性回归算法「建议收藏」

    python线性回归算法「建议收藏」1.线性回归算法2.在Python中实现线性回归那我们如何在Python中实现呢?利⽤Python强⼤的数据分析⼯具来处理数据。Numpy提供了数组功能,以及对数据进⾏快速处理的函数。Numpy还是很多⾼级扩展库的依赖,⽐如Pandas,Scikit_Learn等都依赖于它。Scikit_Learn扩展库,这是⼀个机器学习相关的库。它提供了完善的机器学习⼯具箱,包括数据预处理、分类、回归、预测等。2.1安装sklearn⼯具本⾸先进⼊到虚拟环境cd~/Desktop/env_s

  • 如何用python画一个圆(python主要是做什么的)

    本文为大家分享了python实现画圆功能的实例代码,有需要的朋友请参考下文!importnumpyasnpimportmatplotlib.pyplotaspltfrommatplotlib.patchesimportPolygonimportmatplotlib.patchesasmpatchesfig=plt.figure(figsize=(16,8))ax=…

发表回复

您的电子邮箱地址不会被公开。

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