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)


相关推荐

  • route -add_route -n命令结果详解

    route -add_route -n命令结果详解routeadd192.168.5.0mask255.255.255.0192.168.12.8routeadd命令的主要作用是添加静态路由,通常的格式是:routeADD157.0.0.0MASK255.0.0.0 157.55.80.1METRIC3IF2参数含义:^destination ^mask   ^gateway  met

  • 基础乐理

    基础乐理

  • mediumtext java_加快此查询(加入mediumtext字段)「建议收藏」

    mediumtext java_加快此查询(加入mediumtext字段)「建议收藏」我有这个问题SELECTt.name,t.userid,t.date,t.cat_id,t.forum_id,t.reply,t.hidden,t.moderated,t.sticky,t.statut,t.poll,t.helpful,t.del,t_data.message,user.nameASauthor_name,user.levelASauthor_level,user….

  • strtok 函数

    strtok 函数C库函数-strtok()C标准库-<string.h>描述C库函数char*strtok(char*str,constchar*delim)分解字符串str为一组字符串,delim为分隔符。声明下面是strtok()函数的声明。char*strtok(char*str,constchar*delim)…

  • python错误和异常处理_python异常处理

    python错误和异常处理_python异常处理抛出异常Python使用raise语句抛出一个指定的异常。raise语法格式如下:raise[Exception[,args[,traceback]]]defdivision():”’功能:分苹果”’print(“\n=====================分苹果了=====================\n”)apple=int(input(“请输入苹果的个数:”))#.

  • pycharm2021.3.2激活码【2021.8最新】

    (pycharm2021.3.2激活码)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html…

发表回复

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

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