lv14 内核定时器 11

news2025/1/12 2:58:18

一、时钟中断

硬件有一个时钟装置,该装置每隔一定时间发出一个时钟中断(称为一次时钟嘀嗒-tick),对应的中断处理程序就将全局变量jiffies_64加1

jiffies_64 是一个全局64位整型, jiffies全局变量为其低32位的全局变量,程序中一般用jiffies

HZ:可配置的宏,表示1秒钟产生的时钟中断次数,一般设为100或200

二、延时机制

  1. 短延迟:忙等待

    1. void ndelay(unsigned long nsecs)
    2. void udelay(unsigned long usecs)
    3. void mdelay(unsigned long msecs)
  2. 长延迟:忙等待

    使用jiffies比较宏来实现

    time_after(a,b)    //a > b
    time_before(a,b)   //a < b
    ​
    //返回值为布尔类型。例如 time_after(a,b) 表示时间戳 a 是否晚于时间戳 b
    
    //延迟100个jiffies
    unsigned long delay = jiffies + 100;
    while(time_before(jiffies,delay))
    {
        ;
    }
    ​
    //延迟2s
    unsigned long delay = jiffies + 2*HZ;
    while(time_before(jiffies,delay))
    {
        ;
    }

  3. 睡眠延迟----阻塞类

    void msleep(unsigned int msecs);  //深度睡眠
    
    unsigned long msleep_interruptible(unsigned int msecs); //浅度睡眠

延时机制的选择原则:

  1. 异常上下文中只能采用忙等待类

  2. 任务上下文短延迟采用忙等待类,长延迟采用阻塞类

三、定时器

(1)定义定时器结构体

struct timer_list 
{
    struct list_head entry;   //链表管理很多个定时器,每个定时器都有超时值
    unsigned long expires;  // 期望的时间值 jiffies + x * HZ
    void (*function)(unsigned long); // 时间到达后,执行的回调函数,软中断异常上下文
    unsigned long data;      //传给回调函数的实参,常转换为地址来使用
};

其中list_head是链表,内核中通过链表管理很多个定时器,每个定时器都有一个超时值。硬件时钟每经过一个时间,都会进行一个计数,同时触发一个软中断,在软中断服务程序中(异常上下文,不能调用任何可能引起阻塞的函数),会进来查看定时器是否超时了,通过现在的tick值与超时tick值进行比较,如果超时了会触发回调函数,其中data用来给回调函数传实参。

(2)初始化定时器

init_timer(struct timer_list *)

(3)增加定时器 ------ 定时器开始计时

void add_timer(struct timer_list *timer);

加入到链表,定时器才开始工作

(4)删除定时器 -------定时器停止工作

int del_timer(struct timer_list * timer);

(5)修改定时器

一般如果想实现每隔x秒钟设置一个闹钟,需要把这个函数放在回调函数的最后。

 int mod_timer(struct timer_list *timer, unsigned long expires);

 

模板

定义struct timer_list tl类型的变量


init_timer(...);//模块入口函数

//模块入口函数或open或希望定时器开始工作的地方
tl.expires = jiffies + n * HZ //n秒
tl.function = xxx_func;
tl.data = ...;

add_timer(....);


//不想让定时器继续工作时
del_timer(....);

void xxx_func(unsigned long arg)
{
	......
	mod_timer(....);//如需要定时器继续隔指定时间再次调用本函数
}

四、课堂练习—秒设备

目标:在应用程序测试读取字符设备时打印秒计数值

second.c,中断定时器不能被多个应用打开,所以使用原子来防止被重复打开

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>


int major = 11;
int minor = 0;
int mysecond_num  = 1;

struct mysecond_dev
{
	struct cdev mydev;

	atomic_t openflag;//1 can open, 0 can not open

	struct timer_list timer;
	int second;
	
};

struct mysecond_dev gmydev;

void timer_func(unsigned long arg)
{
	struct mysecond_dev * pmydev = (struct mysecond_dev *)arg;
	pmydev->second++;
	mod_timer(&pmydev->timer, jiffies + 1 * HZ);

}

int mysecond_open(struct inode *pnode,struct file *pfile)
{
	struct mysecond_dev *pmydev = NULL;

	pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mysecond_dev,mydev));
	
	pmydev = (struct mysecond_dev *)pfile->private_data;

	//运算后结果为0则返回真,否则返回假表示设备已经被打开
	if(atomic_dec_and_test(&pmydev->openflag))
	{
		pmydev->timer.expires = jiffies + 1 * HZ;
		pmydev->timer.function = timer_func;
		pmydev->timer.data = (unsigned long)pmydev;
		add_timer(&pmydev->timer);

		return 0;
	}
	else
	{
		atomic_inc(&pmydev->openflag);
		printk("The device is opened already\n");
		return -1;
	}
}

int mysecond_close(struct inode *pnode,struct file *pfile)
{
	struct mysecond_dev *pmydev = (struct mysecond_dev *)pfile->private_data;

	del_timer(&pmydev->timer);
	atomic_set(&pmydev->openflag,1);

	return 0;
}

int mysecond_read(struct file *pfile,char __user *puser, size_t size, loff_t *p_pos)
{
	struct mysecond_dev *pmydev = (struct mysecond_dev *)pfile->private_data;
	int ret = 0;

	if(size < sizeof(int))
	{
		printk("the expect read size is invalid\n");
		return -1;
	}
	if(size >= sizeof(int))
	{
		size= sizeof(int);
	}

	ret = copy_to_user(puser, &pmydev->second, size);
	if(ret)
	{
		printk("copy to user failed\n");
		return -1;
	}

	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mysecond_open,
	.release = mysecond_close,
	.read = mysecond_read,
};

int __init mysecond_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major,minor);

	/*申请设备号*/
	ret = register_chrdev_region(devno,mysecond_num,"mysecond");
	if(ret)
	{
		ret = alloc_chrdev_region(&devno,minor,mysecond_num,"mysecond");
		if(ret)
		{
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//容易遗漏,注意
	}

	/*给struct cdev对象指定操作函数集*/	
	cdev_init(&gmydev.mydev,&myops);

	/*将struct cdev对象添加到内核对应的数据结构里*/
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev,devno,mysecond_num);

	//初始化置位1
	atomic_set(&gmydev.openflag,1);

	//初始化定时器
	init_timer(&gmydev.timer);

;	return 0;
}

void __exit mysecond_exit(void)
{
	dev_t devno = MKDEV(major,minor);

	cdev_del(&gmydev.mydev);

	unregister_chrdev_region(devno,mysecond_num);
}


MODULE_LICENSE("GPL");

module_init(mysecond_init);
module_exit(mysecond_exit);

Makefile

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)


modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else

CONFIG_MODULE_SIG=n
obj-m += mychar.o
obj-m += mychar_poll.o
obj-m += openonce_atomic.o
obj-m += openonce_spinlock.o
obj-m += mychar_sema.o
obj-m += mychar_mutex.o
obj-m += second.o

endif

testsecond_app.c

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char * argv[])
{
	int fd = -1;
	int sec;

	if(argc < 2)
	{
		printf("The argument is too few\n");
		return -1;
	}

    //打开字符驱动时,即会启动定时器
	fd = open(argv[1],O_RDONLY);
	if(fd < 0)
	{
		printf("open %s failed \n", argv[1]);
		return 2;
	}

    //延迟
	sleep(3);

	read(fd, &sec, sizeof(sec));
	printf("The second is %d\n",sec);

	close(fd);
	fd = -1;
	return 0;
	
}

编译测试效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1401710.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

web架构师编辑器内容-图层拖动排序功能的开发

新的学习方法 用手写简单方法实现一个功能然后用比较成熟的第三方解决方案即能学习原理又能学习第三方库的使用 从两个DEMO开始 Vue Draggable Next: Vue Draggable NextReact Sortable HOC: React Sortable HOC 列表排序的三个阶段 拖动开始&#xff08;dragstart&#x…

Spring-AOP入门案例

文章目录 Spring-AOP入门案例概念:通知(Advice)切入点(Pointcut )切面&#xff08;Aspect&#xff09; 目标对象(target)代理对象(Proxy)顾问&#xff08;Advisor)连接点(JoinPoint) 简单需求&#xff1a;在接口执行前输出当前系统时间Demo原始未添加aop前1 项目包结构2 创建相…

springCloud的ribbon和feign

ribbon方式调用 就是将原来的具体地址&#xff0c;改为了通过服务名去调用。注册中心中有多个服务&#xff0c;相同服务名&#xff0c;就会算作可以调用的服务。 首先得有一个注册中心&#xff0c;然后各种服务注册进去&#xff0c;然后利用ribbon或者feign去调用。 ribbon是直…

imgaug库图像增强指南(34):揭秘【iaa.Clouds】——打造梦幻般的云朵效果

引言 在深度学习和计算机视觉的世界里&#xff0c;数据是模型训练的基石&#xff0c;其质量与数量直接影响着模型的性能。然而&#xff0c;获取大量高质量的标注数据往往需要耗费大量的时间和资源。正因如此&#xff0c;数据增强技术应运而生&#xff0c;成为了解决这一问题的…

【数据结构与算法】归并排序详解:归并排序算法,归并排序非递归实现

一、归并排序 归并排序是一种经典的排序算法&#xff0c;它使用了分治法的思想。下面是归并排序的算法思想&#xff1a; 递归地将数组划分成较小的子数组&#xff0c;直到每个子数组的长度为1或者0。将相邻的子数组合并&#xff0c;形成更大的已排序的数组&#xff0c;直到最…

Python Timer定时器:控制函数在特定时间执行

Thread类有一个Timer子类&#xff0c;该子类可用于控制指定函数在特定时间内执行一次。例如如下程序&#xff1a; from threading import Timerdef hello():print("hello, world") # 指定10秒后执行hello函数 t Timer(10.0, hello) t.start() 上面程序使用 Timer …

MySQL-B-tree和B+tree区别

B-tree&#xff08;平衡树&#xff09;和Btree&#xff08;平衡树的一种变种&#xff09;是两种常见的树状数据结构&#xff0c;用于构建索引以提高数据库的查询性能。它们在一些方面有相似之处&#xff0c;但也有一些关键的区别。以下是B-tree和Btree的主要区别&#xff1a; …

Macos数据库管理软件:Navicat Premium for Mac 16.3.5中文版

Navicat Premium 16 for Mac是一款强大的数据库管理和开发工具&#xff0c;支持多种数据库系统&#xff0c;如MySQL、Oracle、SQL Server等。它提供了直观的用户界面和丰富的功能&#xff0c;使用户能够轻松地创建、管理和维护数据库。 软件下载&#xff1a;Navicat Premium fo…

【Unity学习笔记】Unity TestRunner使用

转载请注明出处&#xff1a;&#x1f517;https://blog.csdn.net/weixin_44013533/article/details/135733479 作者&#xff1a;CSDN|Ringleader| 参考&#xff1a; Input testingGetting started with Unity Test FrameworkHowToRunUnityUnitTest如果对Unity的newInputSystem感…

不同开发语言在进程、线程和协程的设计差异

不同开发语言在进程、线程和协程的设计差异 1. 进程、线程和协程上的差异1.1 进程、线程、协程的定义1.2 进程、线程、协程的差异1.3 进程、线程、协程的内存成本1.4 进程、线程、协程的切换成本 2. 线程、协程之间的通信和协作方式2.1 python如何实现线程通信&#xff1f;2.2 …

Leetcode3006. 找出数组中的美丽下标 I

Every day a Leetcode 题目来源&#xff1a;3006. 找出数组中的美丽下标 I 解法1&#xff1a;暴力 用 C 标准库中的字符串查找函数&#xff0c;找到 a 和 b 分别在 s 中的起始下标&#xff0c;将符合要求的下标插入答案。 代码&#xff1a; /** lc appleetcode.cn id3006…

云盘后端分析

1.验证码 用的是外面找的 2.发送邮箱验证码 配置邮箱的授权码 我们在发送邮箱的时候&#xff0c;需要把那个值传到数据库中&#xff0c;数据库中有它的状态&#xff0c;我们需要根据状态判断它是注册还是找回密码 我们在发送邮箱之前&#xff0c;先从session里面得到我们验证…

选择排序(二)——堆排序(性能)与直接选择排序

目录 一.前言 二.选择排序 2.1 堆排序 2.2选择排序 2.2.1 基本思想 2.2.2直接选择排序 三.结语 一.前言 本文给大家带来的是选择排序&#xff0c;其中选择排序中的堆排序在之前我们已经有过详解所以本次主要是对比排序性能&#xff0c;感兴趣的友友可移步观看堆排&#…

GPTs Store 推荐的学术类应用,效果怎么样?

&#xff08;注&#xff1a;本文为小报童精选文章&#xff0c;已订阅小报童或加入知识星球「玉树芝兰」用户请勿重复付费&#xff09; 哪些 GPTs &#xff0c;会令我们眼前一亮&#xff1f; 最近 GPTs Store 已经正式发布&#xff0c;提供了推荐应用和各分类板块目前的热门趋势…

LTC2944库仑计(电量计)芯片应用笔记(Arduino,ESP32)

一、一些基础知识 1.蓄电池的容量单位 &#xff08;1&#xff09;毫安时mAH 蓄电池的容量一般会采用毫安时&#xff08;mAH&#xff09;为单位&#xff0c;比如2000mAH的蓄电池意思是该蓄电池理论上可以以2000mA的电流持续放电1小时&#xff0c;2000mA*1H2000mAH。当然这个是…

STM32CubeMX配置定时器输入捕获功能

STM32CubeMX配置定时器输入捕获功能 0.前言一、方法简介二、STM32CubeMX配置1.生成PWM信号2.配置TIM3_CH1进行采样3.占空比计算 三、总结 参考文章&#xff1a;CubeMX系列教程——11 定时器输入捕获 0.前言 最近在学习江科大STM32教程的原理部分时&#xff0c;发现该教程中使用…

1 - 搭建Redis数据库服务器|LNP+Redis

搭建Redis数据库服务器&#xff5c;LNPRedis 搭建Redis数据库服务器相关概念Redis介绍安装RedisRedis服务常用管理命令命令set 、 mset 、 get 、 mget命令keys 、 type 、 exists 、 del命令ttl 、 expire 、 move 、 flushdb 、flushall 、save、shutdown 配置文件解析 LNP …

AlmaLinux 9.3 安装图解

风险告知 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;本次安装图解是在一个全新的演示环境下进行的&#xff0c;演示环境中没有任何有价值的数据&#xff0c;但这并不代表摆在你面前的环境也是如此。生产环境…

深度学习记录--Momentum gradient descent

Momentum gradient descent 正常的梯度下降无法使用更大的学习率&#xff0c;因为学习率过大可能导致偏离函数范围&#xff0c;这种上下波动导致学习率无法得到提高&#xff0c;速度因此减慢(下图蓝色曲线) 为了减小波动&#xff0c;同时加快速率&#xff0c;可以使用momentum…

R语言学习case5:NC基于R语言的UpSetR

step1: 安装库 install.packages("UpSetR")step2:导入包 library(UpSetR)step3&#xff1a;读取数据 otu_RA <- read.delim(./otu_RA.txt, header TRUE, row.names 1, sep \t)read.delim(): 这是R语言中的一个函数&#xff0c;用于读取文本文件&#xff0c;…