13 定时器

news2024/12/24 16:45:26

13 定时器

  • 1、定时
    • 1.1 硬件定时器的特性
    • 1.2 硬件定时器对应的中断处理函数所作的工作(了解)
    • 1.3 linux内核中跟时间相关的三个概念:
  • 2、延时
    • 2.1.延时定义
    • 2.2 忙等待
    • 2.3.休眠等待
    • 2.4 等待队列机制
      • 2.4.1 介绍
      • 2.4.2 结论
      • 2.4.3 进程休眠和唤醒的编程步骤
        • 方法 1
        • 方法 2
  • 3、按键去抖动

1、定时

1.1 硬件定时器的特性

  • 硬件定时器本质就是一个硬件电路(可以是处理器内部集成的定时器控制器或者外置的硬件电路)
  • 一旦启动硬件定时器,硬件定时器按照一定的频率周期性的给CPU核发送中断信号 - 此中断又称定时器中断
  • 发送中断信号的频率(周期)在软件上是可以配置的
    STM32定时器中断触发的周期公式:(PSC + 1) * (ARR + 1) / 时钟频率

1.2 硬件定时器对应的中断处理函数所作的工作(了解)

此函数已经由linux内核默认完成,此函数完成的工作如下:
1.不断的更新系统的运行时间,jiffies_64加1
2.不断更新系统的实际时间(wall-time,时分秒)
3.检查当前进程的时间片是否用完,如果用完,让进程调度器重新分配CPU资源给其他进程
4.检查当前内核中是否有超时的软件定时器,如果有超时的软件定时器,内核调用软件定时器的处理函数
5.统计系统资源,例如:执行top命令,可以看到CPU的利用率,内存使用信息等

1.3 linux内核中跟时间相关的三个概念:

  1. HZ:它是linux内核的全局常量
    ARM架构:HZ=100
    X86架构:HZ=1000
    以ARM架构为例,HZ=100,表示硬件定时器会每一秒钟给CPU核发送100次定时器中断信号,每发生一次中断的时间间隔为10ms
    例如:5* HZ=5*100=500(次硬件定时器中断)=5秒钟
    HZ/2=500ms
  2. jiffies_64:它是linux内核的全局变量,它的数据类型是unsigned long long(64位),它记录系统自开机以来,硬件定时器给CPU发送的定时器中断次数,硬件定时器每发生一次中断jiffies_64加1
    例如:对于ARM架构,每个10ms,jiffies_64加1
  3. jiffies:它也是linux内核全局变量,它的数据类型是unsigned long(32位),它的值取jiffies_64的低32位,也就是每发生一次定时器中断,jiffies也会加1,一般它用于计算时间间隔!
    将来只要在程序中看到jiffies,就是表示当前时刻的时间!
    例如:
    unsigned long timeout = jiffies + 2*HZ;
    说明:
    jiffies:就是表示当前时刻的时间
    2*HZ:2秒
    timeout:表示2秒以后那个时刻的时间
    • 案例
案例:分析以下代码存在的漏洞
unsigned long timeout = jiffies + 5*HZ;
//后面有一堆的代码,CPU执行这些代码需要消耗时间
...
...
//执行完以上代码之后,CPU核判断是否发生了超时现象
if(jiffies > timeout) 
	超时;
else
	没有超时; 
存在的问题:当中间代码执行的时间太长,jiffies此时已经溢出,则此时的jiffies就小于timeout,从而显示系统没有超时引起故障
问:如何解决此问题呢?
答:利用内核提供的宏函数来解决回滚溢出的问题
	   time_after或者time_before
解决以后的代码:
unsigned long timeout = jiffies + 5*HZ;
//后面有一堆的代码,CPU执行这些代码需要消耗时间
...
...
//执行完以上代码之后,CPU核判断是否发生了超时现象
if(time_after(jiffies, timeout)) 
	超时;
else
	没有超时;
  1. linux内核软件定时器
  • 特点:
    1:内核软件定时器可以指定一个超时时间,一旦超时时间到期,内核自动调用其超时处理函数,并且内核自动将超时的定时器删除,所以内核的软件定时器的超时处理函数只执行一次。
    2:基于软中断实现,所以其超时处理函数不能进行休眠操作!
    内核描述软件定时器属性的结构体:
struct timer_list {
	unsigned long expires;
	void (*function)(unsigned long data);
	unsigned long data;
	...
};	
expires:指定软件定时器超时时刻的时间
	例如:expires = jiffies + 5*HZ;
function:指定超时处理函数,基于软中断实现,不能休眠
	  形参data:保存给超时处理函数传递的参数
data:给超时处理函数传递的参数

配套函数:

//初始化软件定时器对象
init_timer(&定时器对象);
//但是还需要额外自己初始化:expires,function,data
定时器对象.expires = ....; //指定超时时刻的时间
定时器对象.function = ...; //指定超时处理函数
定时器对象.data = ...; //指定给超时处理函数传递的参数,不传参不用初始化
	
//向内核注册添加定时器对象,一旦注册成功就开始倒计时
//一旦倒计时为0,内核调用其超时处理函数并且删除定时器对象
add_timer(&定时器对象);
//从内核中删除定时器对象	
del_timer(&定时器对象);
//修改定时器的超时时间
mod_timer(&定时器对象, 新的超时时间);
注意:
	此函数等价于调用三步骤:
	1.先删除之前的定时器:del_timer
	2.重新修改定时器的超时时间:expires = jiffies + xxxx;
	3.重新向内核添加定时器:add_timer

案例1:利用内核软件定时器,不要利用字符设备或者混杂设备驱动编程框架,实现内核程序每隔2秒打印一句话

#include <linux/init.h>
#include <linux/module.h>
// 定义定时器对象
static struct timer_list timer;
// 定义超时函数
static void timer_function(unsigned long data){
	printk("超时到了\n");
	mod_timer(&timer,jiffies + 2*HZ);
}

// 测试传递参数
static int data=0x55;
static int time_init(void){
	// 初始化定时器
	init_timer(&timer);
	timer.expires=jiffies +2*HZ;
	timer.function=timer_function;
	timer.data=&data;
	//注册定时器
	add_timer(&timer);
	return 0;
}

static void time_exit(void){
	// 删除定时器
	del_timer(&timer);
}

module_init(time_init);
module_exit(time_exit);
MODULE_LICENSE("GPL");

2、延时

2.1.延时定义

又称等待,等待某个事件满足要求,不满足则让程序停一停,等一等,如果满足要求则继续执行

  • 等待分两种:忙等待和休眠等待

2.2 忙等待

  • 特点
    1.CPU原地空转,死等某个事件满足要求
    2.忙等待用于等待时间极短的场合:ns,us,ms(10ms以内)
    3.中断和进程都可以使用(硬件中断处理函数,tasklet,工作队列,普通的进程)
  • 涉及的函数:
void ndelay(int ns) //纳秒级忙等待
例如:ndelay(10) //cpu原地空转10纳秒
void udelay(int us) //微秒级忙等待
例如:udelay(10) //cpu原地空转10微秒
void mdelay(int ms) //毫秒级忙等待
例如:mdelay(5) //cpu原地空转5毫秒

2.3.休眠等待

  • 特点:
    1.休眠等待只能用于进程,不能用于中断,进程休眠是指进程会释放掉占用的CPU资源给其他进程
    2.应用于等待时间较长或者随机场合
  • 涉及的函数:
回顾应用程序休眠的函数:sleep(10)
void msleep(int ms) //毫秒级休眠等待	
void ssleep(int s) //秒级休眠等待	
例如:msleep(500) //进程休眠等待500毫秒
说明:应用程序也就是进程利用系统调用函数可以陷入内核空间,然后进程由用户空间切换到内核空间继续运行,一旦进程在内核空间调用此函数立马释放占用的CPU资源给其他进程而这个进程进入休眠状态,等待被唤醒,唤醒的方法有两种:
	1.进程休眠的时间到期(500毫秒到期),内核主动来唤醒休眠的进程,进程继续运行
	2.进程在休眠期间接收到了kill信号,进程会被立即唤醒,但是结果很悲催:进程被唤醒之后立刻死去
	
schedule() //永久性休眠
说明:当应用程序也就是进程利用系统调用函数可以陷入内核空间,然后进程由用户空间切换到内核空间继续运行,一旦进程在内核空间调用此函数立马释放占用的CPU资源给其他进程,而这个进程进入休眠状态,等待被唤醒,唤醒的方法就一种:
	1.进程在休眠期间接收到了kill信号,进程会被立即唤醒,但是结果很悲催:进程被唤醒之后立刻死去
schedule_timeout(5*HZ); //休眠等待5秒钟,时间单位是硬件定时器中断触发的次数
schedule_timeout(5); //休眠等待50毫秒钟,时间单位是硬件定时器中断触发的次数
说明:应用程序也就是进程利用系统调用函数可以陷入内核空间,然后进程由用户空间切换到内核空间继续运行,一旦进程在内核空间调用此函数立马释放占用的CPU资源给其他进程而这个进程进入休眠状态,等待被唤醒,唤醒的方法有两种:
	1.进程休眠的时间到期(5秒或者50毫秒到期),内核主动来唤醒休眠的进程,进程继续运行
	2.进程在休眠期间接收到了kill信号,进程会被立即唤醒,但是结果很悲催:进程被唤醒之后立刻死去

思考:以上休眠函数有个致命的缺陷:
进程调用这些函数可以做到随时随地休眠,但是做不到随时随地被唤醒还能正常运行(而不是kill去死),因为有些场合,等待某个事件满足要求,这个事件满足要求它可能是随机的,可能随时就能够满足你的要求,那么此时就需要立刻唤醒休眠的进程并且让他正常运行处理到来的事件!
问:如何解决这种致命的缺陷呢?如何做到让进程随时随地休眠并且随时随地被唤醒还能正常运行呢?
答:利用等待队列机制!

2.4 等待队列机制

2.4.1 介绍

等待队列在什么时候,什么场合会使用呢?
举例子阐述说明:以CPU读取UART接收缓冲区数据为例:
CPU读取数据的流程如下:
1.首先启动一个应用程序(进程),进程调用read或者ioctl来从UART接收缓冲区读取数据(read(fd, buf, 1024))
2.然后进程由于调用了read或者ioctl,进程立刻陷入内核空间调用底层驱动的read或者ioctl接口(uart_read)
3.底层驱动的read或者ioctl立刻去UART接收缓冲区获取数据,但是由于UART接收移位器接收数据的速度很慢,此时数据还没有准备好,此时就需要等待,然后我们想到轮询方式,但是此方法会让CPU做大量无用功,降低了CPU的利用率,然后就是采用中断方式
问:此时进程在底层驱动的read或者ioctl接口函数中干嘛呢?
答:有两种选择

  • 1.不等待:
    如果发现数据没有准备就绪,不进行等待延时操作,进程立刻返回,回到用户空间
应用程序:(open("a.txt", O_RDWR|NON_BLOCK))
底层驱动代码:
	xxx_read(....) {
		if(如果数据没有准备就绪并且采用非阻塞方式读取数)
			return -EAGAIN; //直接返回到应用程序
	}
如果返回到应用程序,如果应用还想读取数据,应用程序只需重复调用read或者ioctl来读取数据即可	
  • 2.等待(阻塞):
    如果发现数据没有准备就绪,那么可以让进程在底层驱动的read或者ioctl接口函数中进行等待操作,此等待必然用休眠等待,如果让进程休眠等待,又不能调用msleep/ssleep/schedule/schedule_timeout,这是因为这些函数虽然可以让进程进行休眠等待,但是将来一旦数据准备继续了不能让进程随时随地唤醒并且正常运行(时间没有到期来数据了,怎么办?总不能kill杀死吧?),对于此种情况只能采用等待队列机制让进程进行休眠,并且将来数据一旦准备就绪可以随时唤醒休眠的进程并且让进程正常运行读取数据即可。
    问:什么时候才能随时随地唤醒休眠的进程呢?
    答:如果UART接收缓冲区数据准备就绪,UART控制器势必给CPU发送一个中断信号,内核势必调用其中断处理函数,那咱们只需在中断处理函数中唤醒休眠的进程即可,中断到来也是表示数准备就绪了,那么就可以唤醒休眠的进程了,一旦进程被唤醒,进程就可以读取接收到的数据并且将数据拷贝到用户缓冲区然后返回即可,至此应用程序的read或者ioctl函数完成数据的一次读取操作
    结论:等待队列实现进程在内核空间休眠并且随时被唤醒这个操作就是阻塞方式,应用程序open时,默认采用的就是阻塞方式!

2.4.2 结论

  • 有中断的地方必然有等待队列
    如果事件不满足,利用等待队列让进程休眠,如果事件一旦满足,产生中断,利用中断来唤醒休眠的进程
  • 有等待队列的地方,不一定有中断
  • 等待队列可以让进程随时随地休眠并且随时随地唤醒休眠的进程!

2.4.3 进程休眠和唤醒的编程步骤

方法 1
  1. 定义初始化等待队列头对象
wait_queue_head_t  wq; //定义等待队列对象
init_waitqueue_head(&wq); //初始化等待队列头对象
  1. 定义初始化装载要休眠进程的容器
wait_queue_t  wait; //定义容器
init_waitqueue_entry(&wait,  current);//把当前进程添加到wait容器中
说明:
	"当前进程":正在获取CPU资源并且运行中的进程
	current:它是linux内核的全局指针变量:struct task_struct  *current;
	对应的结构体类型:
		struct task_struct {
			volatile long state;	//记录进程的状态
			pid_t pid;//记录进程的PID号
			char comm[TASK_COMM_LEN];//进程的名字
			...
		};
		- 功能:此结构体用来描述linux系统进程的各种属性信息每当创建一个进程时(./helloworld或者fork或者pthread_create等),linux内核就会自动用这个结构体创建一个对象并且初始化对象来描述你新创建的进程的各种属性信息
		- 结论:current指针就是指向当前进程对应的task_struct结构体对象,将来底层驱动利用current就能够获取到当前进程的各种属性了:
		printf("进程{%s}{%d}\n", current->comm, current->pid);
	注意:一个wait容器对应一个进程,所以wait对象的定义初始化代码一定是局部变量,不能是全局变量	
	例如:底层驱动参考代码
	xxx_read(...) {
		wait_queue_t  wait; //定义容器
		init_waitqueue_entry(&wait,  current);//把当前进程添加到wait容器中,构造一个小鸡	
		...
	}
只要一个应用程序调用read,最终都会调用到底层驱动的唯一的xxx_read函数,而xxx_read函数上来就给这个进程分配一个容器并且添加到这个容器中!
  1. 将要休眠的进程添加到等待队列中
add_wait_queue(&wq, &wait); // 注意:此时进程还没有休眠
  1. 设置进程休眠的类型
    明确:linux系统中,进程休眠的类型有两种:
    1.不可中断的休眠类型:进程在休眠期间,如果接收到了kill信号,不会被立刻唤醒,而是等内核来主动唤醒休眠的进程之后再去处理之前接收到的kill信号,例如:驱动的中断处理函数来唤醒休眠的进程,这叫内核主动唤醒
    结论:不可中断的休眠进程被唤醒的方法只有一种:内核主动来唤醒
    问:何为内核主动来唤醒呢?
    答:就是驱动程序调用一个唤醒函数来唤醒休眠的进程
    2.可中断的休眠类型:进程在休眠期间,如果接收到了kill信号,进程会被立刻唤醒然后处理接收到的kill信号
    结论:可中断的休眠进程被唤醒的方法有两种:
    a) 内核来主动唤醒
    b) 接收到kill信号来唤醒
    设置进程休眠类型的方法:
set_current_state(TASK_INTERRUPTIBLE); //可中断类型
set_current_state(TASK_UNINTERRUPTIBLE);//不可中断类型
// 注意:此时进程还没有休眠
  1. 然后当前要休眠的进程调用以下函数即可完成最终的休眠:
schedule();//进程一旦调用此函数,立刻进入休眠状态,此时释放占用的CPU资源,并且代码停止不前,静静等待被唤醒,一旦被唤醒,进程立刻继续向下运行
  1. 一旦进行被唤醒,进程立马从schedule函数返回继续向下运行,首先设置进程的状态由休眠状态改为运行状态:
set_current_state(TASK_RUNNING);
  1. 然后将唤醒的进程从等待队列中移除
remove_wait_queue(&wq, &wait);
  1. 如果之前休眠的类型是可中断的休眠类型,最后要判断唤醒的原因
// 是因为内核主动来唤醒?还是接收到了kill信号引起的唤醒?
if(signal_pending(current)) {
	printk("进程由于接收到了kill信号引起的唤醒,待会儿就要死去!");
	return -ERESTARTSYS; //重启应用
} else {
	printk("进程由内核主动来唤醒.\n");
	//那么进程就可以正常的继续运行
}
  1. 将来一旦事件满足,数据准备就绪则唤醒休眠的进程
    例如:数据准备就绪产生中断,由中断处理函数来唤醒休眠的进程,此过程又称内核主动唤醒或者驱动主动唤醒,则进程继续正常运行
    唤醒函数两个:
wake_up(&wq); //唤醒wq队列中所有的进程
wake_up_interruptible(&wq);//只唤醒休眠类型是可中断的休眠进程
  • 案例1:编写内核程序,实现写write进程来唤醒读read进程
    驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>

// 定义等待队列头对象
static wait_queue_head_t rwq;

// 混杂设备驱动
// 操作函数
static ssize_t btn_read(struct file *file,char __user *buf,size_t count,loff_t *ppos){
	// 定义初始化装载休眠进程的容器
	wait_queue_t wait;
	init_waitqueue_entry(&wait,current);// current 当前进程的
	// 将进行添加到休眠队列
	add_wait_queue(&rwq,&wait);
	// 设置休眠的类型
	set_current_state(TASK_INTERRUPTIBLE);// 可中断类型
	// 进程休眠
	printk("读进程[%s][%d]休眠\n",current->comm,current->pid);
	schedule();
	// 进程被唤醒
	// 设置进程的状态
	set_current_state(TASK_RUNNING);
	// 从队列中移除唤醒的进程
	remove_wait_queue(&rwq,&wait);
	// 判断进程唤醒的原因
	if(signal_pending(current)){
		printk("进程[%s][%d]是收到了kill才唤醒的\n",current->comm,current->pid);
		return -ERESTARTSYS;// 重启应用
	}else{
		printk("进程[%s][%d]由内核主动唤醒\n",current->comm,current->pid);
	}
	return 0;

}

static ssize_t btn_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos){
	// 唤醒进程
	printk("写进程[%s][%d]唤醒读进程\n",current->comm,current->pid);
	wake_up(&rwq);
	return count;
}

static struct file_operations btn_fops={
	.write = btn_write,
	.read = btn_read
};

static struct miscdevice btn_device={
	.name="mybtn", // 生成/dev/mybtn
	.minor = MISC_DYNAMIC_MINOR,// 自动生成次设备号
	.fops = &btn_fops // 挂载操作函数
};

static int wait_init(void){
	// 挂载设备
	misc_register(&btn_device);
	// 初始化头队列
	init_waitqueue_head(&rwq);
	return 0;
} 
static void wait_exit(void){
	// 卸载设备
	misc_deregister(&btn_device);
}


module_init(wait_init);
module_exit(wait_exit);
MODULE_LICENSE("GPL");

应用代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <fcntl.h>

int main(int argc,char *argv[]){
	int fd;
	if(argc !=2){
		printf("Usage: %s <r|w>\n",argv[0]);
		return -1;
	}
	fd=open("/dev/mybtn",O_RDWR);
	if(fd<0){
		printf("open mybtn failed\n");
		return -1;
	}

	if(!strcasecmp(argv[1],"r")){
		read(fd,NULL,0);// 启动读进程
	}else if(!strcasecmp(argv[1],"w")){
		write(fd,NULL,0);
	}
	close(fd);
	return 0;
}
  • 案例2:编写内核程序,编写中断来实现进程的唤醒
    内核程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/uaccess.h>
// 定义
struct key_gpio{
	int gpio;
	char name[20];
	int state;
};
static struct key_gpio key_info[]={
	{
		.name="key_1",
		.gpio=PAD_GPIO_A+28
	},
	{
		.name="key_2",
		.gpio=PAD_GPIO_B+30
	}
};

// 保存中断触发的按键信息
static struct key_gpio *key_inter;

// 混杂设备
// 定义休眠队列
static wait_queue_head_t rwq;
static ssize_t btn_read(struct file *file,char __user *buf,size_t count,loff_t *ppos){
	// 定义休眠容器
	wait_queue_t wait;
	// 将当前进程添加到wait容器中
	init_waitqueue_entry(&wait,current);
	// 将容器添加到休眠队列中
	add_wait_queue(&rwq,&wait);
	// 设置进程休眠的类型
	set_current_state(TASK_INTERRUPTIBLE);// 可中断
	printk("%s  %d 休眠\n",current->comm,current->pid);
	// 休眠
	schedule();// 等待被唤醒
	// 唤醒后设置当前进程状态
	set_current_state(TASK_RUNNING);
	// 从等待队列中删除该线程
	remove_wait_queue(&rwq,&wait);
	// 判断进行唤醒的方式
	if(signal_pending(current)){
		printk("进程收到kill信号被唤醒[%s][%d]\n",current->comm,current->pid);
		return -ERESTARTSYS;
	}else {
		printk("进程由内核主动唤醒[%s][%d]   %s %d\n",current->comm,current->pid,key_inter->name,key_inter->state);
		copy_to_user(buf,key_inter,count);
//		((struct key_gpio *)buf)->name="测试1111"; 
	}
	return count;
};
// 定义操作函数
static struct file_operations btn_fops={
	.read=btn_read
};
// 设备
static struct miscdevice btn_device={
	.name="mybtn",//生成/dev/mybtn文件
	.minor=MISC_DYNAMIC_MINOR,// 自动生成次设备号 主设备号是10
	.fops=&btn_fops
};
// 中断函数
static irqreturn_t key_interrupt(int irq,void *dev){
	// 保存当前触发的按键信息
	key_inter = (struct key_gpio*)dev;
	key_inter->state = gpio_get_value(key_inter->gpio);
	// 唤醒进程
	wake_up(&rwq);
	return IRQ_HANDLED;
}
// 入口函数
static int btn_init(void){
	int i=0,irq;
	// 申请gpio
	for(i=0;i<ARRAY_SIZE(key_info);i++){
		gpio_request(key_info[i].gpio,key_info[i].name);
		gpio_direction_output(key_info[i].gpio,1);
		// 配置中断
		irq = gpio_to_irq(key_info[i].gpio);// 获取中断号
		request_irq(irq,key_interrupt,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,"key_interrupt",&key_info[i]);
	}
	// 加载混杂设备驱动
	misc_register(&btn_device);
	// 初始化休眠队列
	init_waitqueue_head(&rwq);
	return 0;
};
// 出口函数
static void btn_exit(void){
	int i=0,irq;
	for(i=0;i<ARRAY_SIZE(key_info);i++){
		gpio_direction_output(key_info[i].gpio,1);
		gpio_free(key_info[i].gpio);

		// 释放中断
		irq=gpio_to_irq(key_info[i].gpio);
		free_irq(irq,&key_info[i]);
	}
	// 卸载混杂设备驱动
	misc_deregister(&btn_device);
	
};

module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");

内核程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct key_gpio{
	int gpio;
	char name[20];
	int state;
};
int main(){
	int fd;
	struct key_gpio key;
	fd=open("/dev/mybtn",O_RDWR);
	if(fd<0){
		printf("open mybtn failed\n");
		return -1;
	}
	while(1){
		read(fd,&key,sizeof(key));
		printf("%s -->%s\n",key.name,key.state?"关":"开");
	}
	close(fd);
	return 0;
}
方法 2
  1. 定义初始化等待队列头对象
wait_queue_head_t  wq;
init_waitqueue_head(&wq);
  1. 进程直接调用以下宏函数完成休眠等工作:
wait_event(wq, condition);
说明:
	wq:等待队列头对象,代表整个休眠队列
	condition:
		如果为真,进程调用此宏函数是不会进行休眠操作,立即返回继续向下运行
		如果为假,进程调用此宏函数立刻进入不可中断的休眠状态,代码停止不前,等待被唤醒
		当然此种休眠唤醒的方法只有一种:驱动主动来唤醒
或者
wait_event_interruptible(wq, condition);
说明:
	wq:等待队列头对象,代表整个休眠队列
	condition:
		如果为真,进程调用此宏函数是不会进行休眠操作,立即返回继续向下运行
		如果为假,进程调用此宏函数立刻进入可中断的休眠状态,代码停止不前,等待被唤醒
		当然此种休眠唤醒的方法有两种:驱动主动来唤醒和接收到kill信号来唤醒
  1. 事件满足或者外设数据准备继续产生中断来唤醒休闲的进程
wake_up(&wq); //唤醒所有的休眠进程
或者
wake_up_interruptible(&wq);	//唤醒可中断休眠类型的进程
  1. 切记:利用编程方法2实现进程休眠和唤醒的编程框架
//休眠的代码位置
int condition = 0; //初始值为假
... xxx(....) {
	...
	wait_even_interruptible(wq, condition); //根据condition决定让进程是否休眠
	condition = 0; //重新置假,为了下一次能够休眠
	...
}
//唤醒的代码位置
... yyy(....) {
	...
	condition = 1; //必须先置真,为了能够从wait_event_interrupitble中返回,否则又休眠了
	wake_up_interruptible(&wq); //唤醒休眠进程
	...
}
  • 案例
    内核驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/uaccess.h>

// 定义gpio
struct key_gpio{
	int gpio;
	char name[20];
	int state;
};
static struct key_gpio key_info[]={
	{
		.name="key_1",
		.gpio=PAD_GPIO_A+28
	},
	{
		.name="key_2",
		.gpio=PAD_GPIO_B+30
	}
};

// 保存中断触发的按键信息
static struct key_gpio *key_inter;
// 定义休眠标志 为0表示能够休眠 为1表示不能休眠
static int condition = 0;
// 混杂设备
// 定义休眠队列
static wait_queue_head_t rwq;
static ssize_t btn_read(struct file *file,char __user *buf,size_t count,loff_t *ppos){
	// 休眠
	if(wait_event_interruptible(rwq,condition)){
		printk("进程收到kill信号被唤醒[%s][%d]\n",current->comm,current->pid);
		return -ERESTARTSYS;
	}
	condition =0;// 重新置假,为了下一次能够休眠
	printk("进程由内核主动唤醒[%s][%d]   %s %d\n",current->comm,current->pid,key_inter->name,key_inter->state);
	copy_to_user(buf,key_inter,count);
	return count;
};
// 定义操作函数
static struct file_operations btn_fops={
	.read=btn_read
};
// 设备
static struct miscdevice btn_device={
	.name="mybtn",//生成/dev/mybtn文件
	.minor=MISC_DYNAMIC_MINOR,// 自动生成次设备号 主设备号是10
	.fops=&btn_fops
};
// 中断函数
static irqreturn_t key_interrupt(int irq,void *dev){
	// 保存当前触发的按键信息
	key_inter = (struct key_gpio*)dev;
	key_inter->state = gpio_get_value(key_inter->gpio);
	// 唤醒进程
	condition =1;// 必须置1,为了能够从wait_event_interruptible中返回,否则又休眠了
	wake_up(&rwq);
	return IRQ_HANDLED;
}
// 入口函数
static int btn_init(void){
	int i=0,irq;
	// 申请gpio
	for(i=0;i<ARRAY_SIZE(key_info);i++){
		gpio_request(key_info[i].gpio,key_info[i].name);
		gpio_direction_output(key_info[i].gpio,1);
		// 配置中断
		irq = gpio_to_irq(key_info[i].gpio);// 获取中断号
		request_irq(irq,key_interrupt,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,"key_interrupt",&key_info[i]);
	}
	// 加载混杂设备驱动
	misc_register(&btn_device);
	// 初始化休眠队列
	init_waitqueue_head(&rwq);
	return 0;
};
// 出口函数
static void btn_exit(void){
	int i=0,irq;
	for(i=0;i<ARRAY_SIZE(key_info);i++){
		gpio_direction_output(key_info[i].gpio,1);
		gpio_free(key_info[i].gpio);
		// 释放中断
		irq=gpio_to_irq(key_info[i].gpio);
		free_irq(irq,&key_info[i]);
	}
	// 卸载混杂设备驱动
	misc_deregister(&btn_device);
};
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");

应用程序同上

3、按键去抖动

通过软件定时器实现去抖动
在这里插入图片描述

驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/uaccess.h>

struct btn_gpio{
	int gpio;
	char name[10];
	int state;
};
static struct btn_gpio btn_info[]={
	{
		.name="key_1",
		.gpio=PAD_GPIO_A+28
	},
	{
		.name="key_2",
		.gpio=PAD_GPIO_B+30
	}
};

// 定义休眠队列
static wait_queue_head_t rqh;
static int condition=0;
// 记录终端触发的按键
static struct btn_gpio* btn_gpio;
// 混杂设备
static ssize_t btn_read (struct file *file,char __user *buf,size_t count,loff_t *ppos){
	if(wait_event_interruptible(rqh,condition)){
		printk("进程[%s][%d]由kill唤醒\n",current->comm,current->pid);
		return -ERESTARTSYS;
	}
	condition =0;
	copy_to_user(buf,btn_gpio,count);
	return 0;
};
static struct file_operations btn_fops={
	.read=btn_read
};
static struct miscdevice btn_device={
	.name="mybtn",
	.minor =MISC_DYNAMIC_MINOR,
	.fops=&btn_fops
};
// 定义定时器对象
static struct timer_list timer;
// 定义超时函数
static void timer_function(unsigned long data){
	btn_gpio->state = gpio_get_value(btn_gpio->gpio);
	condition =1;
	wake_up(&rqh);
};
// 中断处理函数
static irqreturn_t  btn_interrupt(int irq,void *dev){
	btn_gpio = (struct btn_gpio *)dev;
	mod_timer(&timer,jiffies+msecs_to_jiffies(10));// 超时时间设置为10ms
	return IRQ_HANDLED;
};

// 入口函数
static int btn_init(void){
	// gpio
	int i=0,irq;
	for(i=0;i<ARRAY_SIZE(btn_info);i++){
		gpio_request(btn_info[i].gpio,btn_info[i].name);
		gpio_direction_input(btn_info[i].gpio);

		irq = gpio_to_irq(btn_info[i].gpio);// 中断号
		request_irq(irq,btn_interrupt,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
							btn_info[i].name,&btn_info[i]);// 申请中断资源
	}
	printk("gpio and irq success\n");
	// 加载驱动
	misc_register(&btn_device);
	// 初始化休眠队列
	init_waitqueue_head(&rqh);
	// 初始化定时器
	init_timer(&timer);
	// 超时处理函数
	timer.function=timer_function;
	return 0;
}
// 出口函数
static void btn_exit(void){
	// 释放gpio和中断
	int i,irq;
	for(i=0;i<ARRAY_SIZE(btn_info);i++){
		gpio_free(btn_info[i].gpio);
		irq=gpio_to_irq(btn_info[i].gpio);
		free_irq(irq,&btn_info[i]);
	}
	printk("free gpio itq success \n");
	// 卸载驱动
	misc_deregister(&btn_device);
	// 删除定时器
	del_timer(&timer);
}

module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");

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

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

相关文章

Epic Games 商店面向欧盟 iPhone 用户上线

Epic Games Store 终于在欧盟推出&#xff0c;为玩家提供了不通过 App Store 就能在 iPhone上访问游戏的途径。在经历了漫长而昂贵的关于支付和竞争对手应用程序店面的法律战&#xff0c;以及公证方面的麻烦之后&#xff0c;Epic Games 成功地为App Store 带来了一个数字店面。…

IO多路复用中的水平触发和边缘触发、Java NIO中的水平触发举例

基础原理 水平触发&#xff08;Level-triggered&#xff0c;也被称为条件触发&#xff09;LT&#xff1a;主要满足条件&#xff0c;就触发事件。 边缘触发&#xff08;Edge-triggered&#xff09;ET&#xff1a;当状态变化时触发。 使用脉冲信号来说明LT和ET&#xff1a;LT指…

Nginx--地址重写Rewrite

一、什么是Rewrite Rewrite对称URL Rewrite&#xff0c;即URL重写&#xff0c;就是把传入Web的请求重定向到其他URL的过程 URL Rewrite最常见的应用是URL伪静态化&#xff0c;是将动态页面显示为静态页面方式的一种技术。比如http://www.123.com/news/index.php?id123 使用U…

Linux命令之一

Linux命令之一 帮助类命令磁盘管理文件管理系统设置开关服务命令临时开关服务命令永久开关服务命令 压缩/解压网络通讯网络访问管道和重定向搜索命令grepfind 磁盘分区类命令 Linux命令速查平台 帮助类命令 语法 man [命令或配置文件] &#xff08;功能描述&#xff1a;获得帮助…

【c++】深入理解别名机制--引用

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C 目录 前言 一、引用的概念和定义 二、引用的特性 三、引用的实用性 1.引用传参 2.引用做返回值 2.1 引用做返回值的作用 2.2 引用坍缩问题、悬挂引用问…

ThreadPoolExecutor详解

恰逢经济下行&#xff0c;鄙人工作、生活日趋艰难&#xff0c;原本美好的愿望&#xff0c;如今只能成为奢望。不知如何是好的我&#xff0c;只能精研近几年来因浮躁而荒废的知识。今天就想跟大家聊一个对我来讲看似熟悉实则陌生的工具——ThreadPoolExecutor。熟悉是因为在我负…

【nacos 第二篇章】动手实践(从零代码开发版)

一、环境准备 本章将通过手把手的教程一步一步教你如何从零开发一个微服务应用。 首先需要安装好 nacos 服务并启动。安装 nacos 服务请看作者的 【nacos 第一篇章】安装一下 nacos 文章。 二、初始化项目 如上图所示&#xff0c;可以建立一个基础的项目。 搭建了基础项目之…

计算机毕业设计 在线项目管理与任务分配系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

节点使用简介:comfyui-photoshop

1、安装comfyui-photoshop 略过 一点要注意的是&#xff1a;在Photoshop上的安装增效工具&#xff0c;要通过Creative Cloud 桌面应用程序进行安装&#xff0c;才能成功在增效工具中显示&#xff0c;直接通过将文件解压到Plug-ins路径行不通&#xff08;至少对我来说行不通&am…

通过剪枝与知识蒸馏优化大型语言模型:NVIDIA在Llama 3.1模型上的实践与创新

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

阿里云ACP的三种报名与对应题库获取方式的详细说明(按费用排序)

文章目录 前言方式一、官方途径(较为昂贵)考试资格获取官方视频教程获取方式总结 方式二、报名机构(价格适中&#xff0c;考取速度快)推荐机构大概费用机构报名方式机构报名后所携带的内容或者说对于其他方法有什么优势总结 方式三、闲鱼(最便宜&#xff0c;但题库有风险)考试资…

C语言 之 strstr函数的使用和模拟、strtok函数的使用、strerror函数和perror函数的使用

文章目录 strstr 的使用和模拟实现strstr函数模拟实现 strtok 函数的使用例子1例子2例子3 strerror 函数的使用perror函数 strstr 的使用和模拟实现 函数原型&#xff1a; const char * strstr ( const char * str1, const char * str2 ); 该函数能够查找str1中第一次出现str2…

产业经济大脑建设方案(五)

为了提升产业经济的智能化水平&#xff0c;我们提出建设一个综合产业经济大脑系统&#xff0c;该系统通过整合大数据分析、人工智能和云计算技术&#xff0c;构建全方位的数据采集、处理和决策支持平台。该平台能够实时监测产业链各环节的数据&#xff0c;运用智能算法进行深度…

Unified 阻抗控制 architecture、framework、approach

Unified 阻抗控制&#xff08;Unified Impedance Control&#xff09;作为一种控制策略&#xff0c;其architecture&#xff08;架构&#xff09;、framework&#xff08;框架&#xff09;和approach&#xff08;方法&#xff09;为&#xff1a; 一、Unified 阻抗控制 Archite…

【MPC】模型预测控制 | 车辆优化控制策略研究与实践

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

CorelDRAW X4重磅发布更新!包含最新安装包下载

CorelDRAW X4是矢量图形制作工具软件&#xff0c;这个图形工具给设计师们提供了矢量动画、页面设计、网站制作、位图编辑和网页动画等多种功能。 X4版本的新特性有活动文本格式&#xff1a;仿效现有段落格式化、使文本能够贴齐格线框&#xff0c;或从边框缩排&#xff0c;从而获…

牛客JS题(四十四)根据包名,在指定空间中创建对象

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; 地址引用assign 题干&#xff1a; 我的答案 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><style>/* 填写样式 */</style></head><body><!--…

使用多种机器学习模型进行情感分析

使用 TF-IDF 与贝叶斯分类器进行情感分析是一个常见且有效的组合&#xff0c;特别是在文本分类任务中。贝叶斯分类器&#xff08;通常是朴素贝叶斯分类器&#xff09;等机器学习模型具有计算简单、效率高的优点&#xff0c;且在文本分类任务中表现良好。接下来&#xff0c;我将…

8.16作业

1.思维导图 2.在登录界面的登录取消按钮进行以下设置&#xff1a; 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断…

Solidworks二次开发:通过XYZ点的曲线

在SolidWorks中,通过XYZ点创建曲线是一种根据一组点的坐标生成三维曲线的方法。这种方法适用于需要根据特定点集设计曲线的情况,比如在建模复杂几何形状或执行逆向工程时。在SolidWorks中通过XYZ点创建曲线,操作步骤如下 打开SolidWorks并新建文件:启动SolidWorks软件,新建…