当前位置: 首页 > news >正文

Linux驱动开发笔记(九)——内核定时器

视频:第12.1讲 Linux内核定时器实验-内核时间管理简介_哔哩哔哩_bilibili

文档:《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81.pdf》五十章


 1. 内核时间管理

《指南》50.1.1节:

        Linux内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、定时器。硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率, 也叫做节拍率(tick rate)(有的资料也叫系统频率),比如1000Hz,100Hz等等说的就是系统节拍率。

1.1 设置系统节拍率

        编译Linux内核的时候可以通过图形化界面设置系统节拍率:

cd /.../linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
make menuconfig

        选择kernel features -> Timer frequency,然后便可选择系统街拍率:

        使用命令vi .config去查看:

1.2 系统时间计数器 jiffies

        系统会有一个全局变量jiffies记录启动以来的系统节拍数,在系统启动时初始化为0

        Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会 将jiffies 初始化为0。

        为了兼容32位和64位系统,定义了32位和64位两种jiffies,32位jiffies其实就是64位jiffies的低32位:

// 定义在include/linux/jiffies.h
extern u64 __jiffy_data jiffies_64; 
extern unsigned long volatile __jiffy_data jiffies; 
get_jiffies_64()读取jiffies的值。32位和64位系统都可使用

        如果jiffies达到最大值,就会从零开始计数,称为溢出。若最小HZ=100,则32位的jiffies约497天溢出;最大HZ=1000,则32位的jiffies约49.7天溢出。64位的jiffies有生之年不会溢出。因此对于32位的jiffies,有一个处理溢出的问题。linux提供了相关API函数:

函数名返回值功能
time_after(a, b)(long)(b) - (long)(a) < 0判断a是否在b之后(a > b ?)
time_before(a, b)time_after(b, a)判断a是否在b之前(a < b ?)
time_after_eq(a, b)(long)(a) - (long)(b) >= 0)判断a是否在b之后或等于b(a ≥ b ?)
time_before_eq(a, b)time_after_eq(b, a)判断a是否在b之前或等于b(a ≤ b ?)
一般都将jiffies填到a,要比较的值填到b

为了方便开发,Linux内核提供了几个jiffies和ms、us、ns之间的转换函数:

示例:

        《指南》中的示例,判断是否超时2s以上:

        等待1ms:

	timeout = jiffies + msecs_to_jiffies(1);while (time_before(jiffies, timeout)) {…………}

2. 内核定时器

《指南》50.1.2节:

        Linux内核定时器采用系统时钟来实现,并不是硬件定时器。Linux内核定时器使用很简单,只需要提供超时时间(相当于定时值)和定时处理函数即可,当超时时间到了以后设置的定时处理函数就会执行,不需要做一大堆的寄存器初始化工作。

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

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.1 内核定时器API函数

2.1.1 init_timer

        初始化定时器,但不会启动。

void init_timer(struct timer_list *timer) 
// timer: 要初始化的定时器

2.1.2 add_timer

        向Linux内核注册/启动定时器。注册以后定时器就会开始运行。

        必须先init_timer初始化,并配置好定时器结构体里的参数,再注册。

void add_timer(struct timer_list *timer)
//timer:要注册的定时器

2.1.3 del_timer

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

int del_timer(struct timer_list * timer)
// timer:  要删除的定时器
// return: 0,定时器还没激活;1,定时器已激活

2.1.4 del_timer_sync

        也是删除定时器,但是会等待其他处理器使用完该定时器再删除。

         del_timer_sync不能在中断服务函数使用。

int del_timer_sync(struct timer_list *timer)
// timer:  要删除的定时器
// return: 0,定时器还没激活;1,定时器已激活

2.1.5 mod_timer

        修改定时值。

        如果定时器还没有激活,那么mod_timer函数会激活定时器。

int mod_timer(struct timer_list *timer, unsigned long expires)
//timer:  要修改的定时器。 
//expires:修改后的超时时间 
//return: 0,调用mod_timer函数前定时器未被激活;1,调用mod_timer函数前定时器已
被激活。 

2.1.6 示例

struct timer_list timer; /* 定义定时器 *//* 定时器回调函数 */
void function(unsigned long arg){/** 定时器处理代码*//* 如果需要定时器周期性运行的话就使用mod_timer重新设置超时值并且启动定时器 */mod_timer (&dev->timertest, jiffies + msecs_to_jiffies (2000));
}/* 初始化函数 */
void init(void){init_timer(&timer); /* 初始化定时器 */timer.function = function; /* 设置定时处理函数 */timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间2秒 */timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */add_timer (&timer); /* 注册/启动定时器 */
}/* 退出函数 */
void exit(void){del_timer(&timer); */ 删除定时器 *//* 或者使用del_timer_sync(&timer); */
}

2.2 短延时函数

3. 定时器实验

3.1 文件结构

13_TIMER (工作区)
├── .vscode
│   ├── c_cpp_properties.json
│   └── settings.json
├── 13_timer.code-workspace
├── Makefile
└── timer.c

3.2 Makefile

CFLAGS_MODULE += -wKERNELDIR := /home/for/linux/imx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek  # 内核路径
# KERNELDIR改成自己的 linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek文件路径(这个文件从正点原子“01、例程源码”中直接搜,cp到虚拟机里面)
CURRENT_PATH := $(shell pwd)	# 当前路径
obj-m := timer.o			# 编译文件
build: kernel_modules			# 编译模块
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean	

3.3 timer.c

主要有以下更新:

        增加了 定时器处理函数timer_func()

        增加了 设备结构体中 定时器定义

        增加了 驱动入口函数中 定时器初始化

        增加了 驱动出口函数中 定时器删除

        增加了 LED初始化函数led_init()

 

gpio和pinctrl配置与Linux驱动开发笔记(六)配置一致,这里不再重复(记得设备树注释掉重复的io)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>#define DEV_CNT  1
#define DEV_NAME "timer"/* 设备结构体 */
struct timer_dev{dev_t devid;int major;int minor;struct cdev cdev;struct device *device;struct class *class;struct device_node *nd;struct timer_list timer;int led_gpio;
};
struct timer_dev timerdev;/* 定时器处理函数 */
static void timer_func(unsigned long arg){struct timer_dev *dev = (struct timer_dev*)arg;static int state = 1;state = !state;gpio_set_value(dev->led_gpio, state);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(500));
}
/* 初始化LED */
int led_init(struct timer_dev *dev){int ret = 0;/* 1.获取设备节点 */dev->nd = of_find_node_by_path("/gpioled");  // 找到刚才在imx6ull-alientek-emmc.dts根节点下加入的gpioled节点if(dev->nd == NULL){ret = -EINVAL;goto fail_fd;}/* 2.获取LED对应的GPIO */  // 也就是节点中led-gpios那一行内容dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpios", 0);if(dev->led_gpio < 0){ret = -EINVAL;goto fail_gpio;}/* 3.申请IO */ret = gpio_request(dev->led_gpio, "led-gpios");if(ret){ret = -EBUSY;printk("IO %d busy!\r\n",dev->led_gpio);goto fail_request;}/* 4.使用IO,设置为输出 */ret = gpio_direction_output(dev->led_gpio, 1);if(ret < 0){ret = -EINVAL;goto fail_gpioset;}return 0;fail_gpioset:gpio_free(dev->led_gpio);
fail_request:
fail_gpio:
fail_fd:return ret;
}/* 操作集 */
static int key_release(struct inode *inode, struct file *filp){struct key_dev *dev = filp->private_data;return 0;
}
static int key_open(struct inode *inode, struct file *filp){filp->private_data = &timerdev;return 0;
}
static ssize_t key_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){int ret;return 0;
}
static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos){struct key_dev *dev = filp->private_data;int ret = 0; return 0;}
static const struct file_operations key_fops = {.owner = THIS_MODULE,.write = key_write,.open = key_open,.release = key_release,.read = key_read,
};/* 驱动入口 */
static int __init key_init(void){int ret = 0;/* 1.注册字符设备驱动 */timerdev.devid = 0;if(timerdev.devid){timerdev.devid = MKDEV(timerdev.devid, 0);register_chrdev_region(timerdev.devid, DEV_CNT, DEV_NAME);} else {alloc_chrdev_region(&timerdev.devid, 0, DEV_CNT, DEV_NAME);timerdev.major = MAJOR(timerdev.devid);timerdev.minor = MINOR(timerdev.devid);}/* 2.初始化cdev */timerdev.cdev.owner = THIS_MODULE;  cdev_init(&timerdev.cdev, &key_fops);/* 3.添加cdev */cdev_add(&timerdev.cdev, timerdev.devid, DEV_CNT); // 错误处理先略过了/* 4.创建类 */timerdev.class = class_create(THIS_MODULE, DEV_NAME);if(IS_ERR(timerdev.class)){return PTR_ERR(timerdev.class);}/* 5.创建设备 */timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, DEV_NAME);if(IS_ERR(timerdev.device)){return PTR_ERR(timerdev.device);}/* 6. 初始化LED */ret = led_init(&timerdev);if(ret < 0){ret = -EINVAL;goto fail_ledinit;}/* 7. 初始化定时器 */init_timer(&timerdev.timer);timerdev.timer.function = timer_func;timerdev.timer.expires = jiffies + msecs_to_jiffies(500); // 延迟500mstimerdev.timer.data = (unsigned long)&timerdev;add_timer(&timerdev.timer);return 0;fail_ledinit:
fail_device:class_destroy(timerdev.class);
fail_class:cdev_del(&timerdev.cdev);return ret;
}/* 驱动出口 */
static void __exit key_exit(void){/* 关灯 */gpio_set_value(timerdev.led_gpio, 1);/* 删除定时器*/del_timer(&timerdev.timer);/* 注销字符设备驱动 */cdev_del(&timerdev.cdev);unregister_chrdev_region(timerdev.devid, DEV_CNT);device_destroy(timerdev.class, timerdev.devid);class_destroy(timerdev.class);/* 释放io */gpio_free(timerdev.led_gpio);}module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");

3.4 测试

# VSCODE
make
sudo cp timer.ko /home/for/linux/nfs/rootfs/lib/modules/4.1.15/ -f# 串口
cd /lib/modules/4.1.15/
depmod
modprobe timer.ko  # 此时开发板红灯开始闪烁
http://www.dtcms.com/a/351488.html

相关文章:

  • CSS 优先级:公司组织架构模型
  • css3背景线性渐变:linear-gradient
  • 基于Python+MySQL实现物联网引论课程一个火警报警及应急处理系统
  • 面向 6G 网络的 LLM 赋能物联网:架构、挑战与解决方案
  • 相机激光安全等级和人眼安全
  • 第九届MathorCup高校数学建模挑战赛-D题:钢水“脱氧合金化”配料方案的优化
  • 五自由度磁悬浮轴承同频振动抑制:从机理拆解到传递函数验证的核心方案
  • 【图像算法 - 24】基于深度学习与 OpenCV 实现人员跌倒识别系统(目标检测方案 - 跌倒即目标)
  • Baumer高防护相机如何通过YoloV8深度学习模型实现形状检测器的使用(YOLOv8 Shape Detector)
  • 无人机航拍数据集|第32期 无人机采矿区作业目标检测YOLO数据集202张yolov11/yolov8/yolov5可训练
  • GaussDB 数据库架构师修炼(十八) SQL引擎-计划管理-SPM
  • Windows MCP 安装教程:让 AI 代理与 Windows 系统无缝交互
  • plantsimulation知识点 RGV小车前端与后端区别
  • 数字营销岗位需要具备的能力有哪些
  • 洛谷 P12332 题解
  • 图论入门与邻接表详解
  • 代码随想录Day62:图论(Floyd 算法精讲、A * 算法精讲、最短路算法总结、图论总结)
  • ElementUI之菜单(Menu)使用
  • 美团购物车小球动画效果
  • Docker Compose 使用指南 - 1Panel 版
  • 国产化芯片ZCC3790--同步升降压控制器的全新选择, 替代LT3790
  • 第17章|PowerShell 安全警报——高分学习笔记(运维实战向)
  • Tableau Server高危漏洞允许攻击者上传任意恶意文件
  • 数据库云平台:提升运维效率与降低成本的有效工具
  • 【Ubuntu系统实战】一站式部署与管理MySQL、MongoDB、Redis三大数据库
  • WPS 智能文档,5分钟上手!
  • React学习教程,从入门到精通, React教程:构建你的第一个 React 应用(1)
  • 电力时序预测相关论文
  • 物流配送路径规划项目方案
  • yggjs_rbutton React按钮组件v1.0.0 最佳实践指南