13 定时器

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_timer2.重新修改定时器的超时时间: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;// 申请gpiofor(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;// 申请gpiofor(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));// 超时时间设置为10msreturn IRQ_HANDLED;
};// 入口函数
static int btn_init(void){// gpioint 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.rhkb.cn/news/403471.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于uniapp使用izExif.js 插件问题

需求&#xff1a;1.APP获取图片的属性&#xff0c;得到经纬度信息&#xff0c;然后标注到图片上 我们采用izExif.js 插件&#xff0c;进行获取图片信息&#xff0c;在模拟器测试好好地&#xff0c;但是使用真机测试发现getImageData没有返回信息&#xff0c;去izExif.js源码查…

ubuntu中python 改为默认使用python3,pip改为默认使用pip3

一、安装pip和python&#xff08;有的话可跳过&#xff09; 更新软件源 sudo apt update !!!apt和apt-get apt apt-get、apt-cache 和 apt-config 中最常用命令选项的集合。 部分截图为apt-get&#xff0c;建议直接用apt 安装pip和python ubuntu 18.04和更高版本默认安…

字符串金额转换,字符串手机号屏蔽,身份证信息查看,敏感词替换

2135 在发票上面该写成零佰零拾零万贰仟壹佰叁拾伍元 我们用逆推法可以写成零零零贰壹叁伍->贰壹叁伍->2135 1.遍历获取到每一个数字&#xff0c;然后把大写放到数组里面&#xff0c;将数字当作索引&#xff0c;在数组里面查找大写 package stringdemo;import java.uti…

Jakarta Servlet 到 SpringMVC

Jakarta EE&#xff08;曾被称为Java EE&#xff09;是Java平台企业版&#xff08;Java Platform Enterprise Edition&#xff09;的下一代版本&#xff0c;它在Oracle将Java EE的开发和维护交给Eclipse Foundation后得以重生&#xff0c;并更名为Jakarta EE。Jakarta EE保留了…

Windows采用VS2019实现Open3D的C++应用

1、参考链接 https://blog.csdn.net/qq_31254435/article/details/137799739 但是&#xff0c;我的方法和上述链接不大一样&#xff0c;我是采用VS2019进行编译的&#xff0c;方便在Windows平台上验证各种算法。 2、创建一个VS2019的C Console工程 #include <iostream>…

MT1619 (A/B/C/D 15W-25W)快充电源主控芯片

MT1619 是一款快充电源主控芯片&#xff0c;MT1619内部集成了一颗高集成度、高性能的电流模式 PWM 控制器和一颗功率 MOSFET。MT1619适用于小于 30W 的开关电源。MT1619 具有恒功率功能&#xff0c;特别适用于 PD 充电器、电源适配器等中小功率的开关电源设备。极低的启动电流与…

windows下TortoiseSVN切换账号的方法

前言 在项目开始初期的时候大家会使用一个默认账号,后面会根据需要给每个人分配各自的个人账号,这个时候就需要重登陆新的svn账号,下面就是讲解下怎样在windows下修改登录TortoiseSVN的账号。 方法 1.首先在桌面右键&#xff0c;选择TortoiseSVN-settings 2.进入设置页面&a…

阿里云注册、认证、短信资质、签名、模板申请过程

一、帐号注册 输入“帐号密码注册”中的相关信息即可。 手机号是必须的&#xff0c;先确定好手机号。 正常的可以直接注册成功的。 二、实名认证 注册成功之后&#xff0c;就可以点击上述的“快速实名认证”。 这次选择的是“企业认证”。 有几种方式&#xff0c;如下&#x…

clamp靶机复现

靶机设置 设置靶机为NAT模式 靶机IP发现 nmap 192.168.112.0/24 靶机IP为192.168.112.143 目录扫描 dirsearch 192.168.112.143 访问浏览器 提示让我们扫描更多的目录 换个更大的字典&#xff0c;扫出来一个 /nt4stopc/ 目录 目录拼接 拼接 /nt4stopc/ 发现页面中有很多…

CeresPCL 岭回归拟合(曲线拟合)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 由于在使用最小二乘插值拟合时,会涉及到矩阵求逆的操作,但是如果这个矩阵接近于奇异时,那么拟合的结果就会与我们期望的结果存在较大差距,因此就有学者提出在最小二乘的误差函数中添加正则项,即: 这里我们也可…

【SpringBoot】SpringBoot框架的整体环境搭建和使用(整合Mybatis,Druid,Junit4,PageHelper,logback等)

目录 1.介绍 1.1 配置文件 1.2 目录结构 2.基于SpringBoot的SpringMVC 4.整合Mybatis 5.整合Druid连接池 6.整合Junit4 7.整合Logback 8.整合PageHelper 9.SpringBoot整合Thymeleaf ​编辑 【附录】springboot的pom.xml 1.介绍 Spring框架的优点是方便解耦、简化开…

Python -- GUI图形界面编程—GUI编程实例 博主也在持续学习中[ 持续更新中!!! 欢迎白嫖 也求粉啊啊啊~ ]

本文介绍了GUI的图形界面编程&#xff08;相关视频是哔站上的应该搜这个题目就能找到&#xff09;&#xff0c;文章还是很基础的&#xff0c;反正我是小白从0开始&#xff0c;主要的结构tinkter库、重要组件简介&#xff08;这个不用死记硬背 用的时候再说&#xff09;、Label&…

用于不平衡医疗数据分类的主动SMOTE

一、主动学习如何应用于不平衡数据的处理 首先&#xff0c;主动SMOTE不是像经典的SMOTE那样从训练集中随机选择一个样本作为生成合成样本的轴心点&#xff0c;而是通过不确定性和多样性采样来智能地进行样本选择&#xff0c;这是主动学习的两种技术。 在数据不平衡的情况下&…

Ubuntu上安装Redis的详细教程

1、安装redis 首先&#xff0c;访问Redis官网&#xff0c;点击首页的【Get Started】&#xff0c;然后点击Install Redis on Linux 安装 终端依次输入以下命令&#xff0c;如果过程中没有错误提示&#xff0c;则redis安装完成。 sudo apt install lsb-release curl gpg cu…

计算机科学速成课笔记

计算机速成课个人理解概要 1.计算机的本质 计算机的本质&#xff1a;极其简单的组件&#xff0c;经过一层层复杂的抽象&#xff0c;做出复杂的工作 2.控制电流的技术发展 继电器→真空管→晶体管 拥有了细微控制电流的手段 tip:早期机械计算机&#xff0c;被虫子(bug)卡住…

基于SpringCloud的能源管理系统-能源管理平台源码-能源在线监测平台-双碳平台源码-SpringCloud全家桶-能管管理系统源码

介绍 适用于高能耗企业、建筑、工厂、园区的水、电、气、热、油、空压机等能源数据采集、分析、报表&#xff1b; 基于SpringCloud的能源管理系统-能源管理平台源码-能源在线监测平台-双碳平台源码-SpringCloud全家桶-能管管理系统源码 软件架构 软件功能 数字大屏 使用说明

机器学习第十一章-特征选择与稀疏学习

11.1子集收集与评价 属性称为"特征" &#xff0c;对当前学习任务有用的属性称为"相关特征" 、没什么用的属性称为"无关特 征" . 从给定的特征集合中选择出相关特征于集的过程&#xff0c;称为"特征选择"。 特征选择是一个重要的"…

Linux | Linux进程万字全解:内核原理、进程状态转换、优先级调度策略与环境变量

目录 1、从计算机组成原理到冯诺依曼架构 计算机系统的组成 冯诺依曼体系 思考&#xff1a;为什么计算机不能直接设计为 输入设备-CPU运算-输出设备 的结构&#xff1f; 2、操作系统(Operator System) 概念 设计OS的目的 描述和组织被管理对象 3、进程 基本概念 进程id和父进程…

数据结构入门——07堆

1.堆 堆&#xff08;Heap&#xff09;是一种特殊的完全二叉树数据结构&#xff0c;具有以下两个主要特性&#xff1a; 结构特性&#xff1a; 堆是一棵完全二叉树&#xff0c;即除了最后一层的叶子节点外&#xff0c;每一层都是满的&#xff0c;最后一层的叶子节点从左向右依次…

志愿服务管理系统--论文pf

TOC springboot360志愿服务管理系统--论文pf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本的广…