13 定时器

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");
  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

启航zpyl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值