Linux驱动之等待队列

文章介绍了Linux内核中等待队列的概念和使用,如wait_event、wake_up等函数,以及如何利用它们实现按键驱动,当按键事件发生时唤醒进程。示例代码展示了一个简单的按键驱动程序,包括中断处理和定时器功能。
摘要由CSDN通过智能技术生成

等待队列用于使进程等待某一特定的事件发生而无需频繁的轮询,在不需要执行任务的时候,我们就让任务进程休眠,直到条件改变时,我们再唤醒他,执行完毕后继续让它睡眠。

例如,我们编写一个按键驱动,当按键按下,内核需要向用户上报数据。但用户又不知道按键什么时候按键,总不能什么都不干,一直轮询读取设备文件吧。这时候就需要使用等待队列,当按键事件没有发生时,就睡眠,一旦按键被按下了,就把进程唤醒向用户上报数据。

使用等待队列需要的头文件:

#include <linux/wait.h>

#include <linux/sched.h>

在Linux中,等待队列以循环链表为基础结构,用了两个数据结构来描述一个等待队列:wait_queue_head_t 和 wait_queue_t。

//队列头部
struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};

//队列成员
struct __wait_queue {
    unsigned int flags;
    void *private;
    wait_queue_func_t func;
    struct list_head task_list;
};

 初始化等待队列。

//动态初始化
    wait_queue_head_t wqh;
    init_waitqueue_head(&wqh);
//静态初始化
    DECLARE_WAIT_QUEUE_HEAD(wqh);    

等待事件:

wait_event(wq_head, condition)
wait_event_timeout(wq_head, condition, timeout) 
wait_event_interruptible(wq_head, condition)
wait_event_interruptible_timeout(wq_head, condition, timeout)

以上宏是以wait_event()为原型,加以特殊条件改造。wait_even()t可用于实现简单的进程休眠,等待直至某个条件成立被唤醒。使用wait_event() ,进程将被置于非中断休眠,而使用wait_event_interruptible()时,进程可以被信号中断wait_event_timeout和wait_event_interruptible_timeout会使进程只等待限定的时间(以jiffies表示,给定时间到期时,宏均会返回0,而无论 condition 为何值)。

唤醒队列:

wake_up(&wq_head)
wake_up_interruptible(&wq_head)
wake_up_interruptible_nr(&wq_head, nr)
wake_up_interruptible_all(&wq_head)

wake_up()可以用来唤醒等待队列上的所有进程,而wake_up_interruptible()只会唤醒那些执行可中断休眠的进程。因此约定,wait_event()和wake_up()搭配使用,而wait_event_interruptible()和wake_up_interruptible()搭配使用。

这样就可以使用一个简单的等待队列,来实现前面所说的按键驱动了。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/ioctl.h>
#include <asm/gpio.h>
#include <mach/soc.h>
#include <mach/platform.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <linux/sched.h>


struct btn_dest{
	int gpio;//gpio端口号
	char *name;//名称
	char code;//键值(代表哪个按键)
};

//定义btn的硬件信息
struct btn_dest btn_info[] = {
	[0] = {
		.gpio = PAD_GPIO_A+28,
		.name = "K2",
		.code = 0x50,
	},
	[1] = {
		.gpio = PAD_GPIO_B+9,
		.name = "K6",
		.code = 0x60,
	},
	[2] = {
		.gpio = PAD_GPIO_B+30,
		.name = "K3",
		.code = 0x70,
	},
	[3] = {
		.gpio = PAD_GPIO_B+31,
		.name = "K4",
		.code = 0x80,
	}
};

//内核定时器
struct timer_list btn_timer;
//声明等待队列头
wait_queue_head_t wqh;
//代表键值和状态
char key = 0;
//按键事件发生标志
int flag = 0;//默认没有发生0-没有 1-发生

/*
inode是文件的节点结构,用来存储文件静态信息
文件创建时,内核中就会有一个inode结构
file结构记录的是文件打开的信息
文件被打开时内核就会创建一个file结构
*/
int btn_open(struct inode *inode, struct file *filp)
{
	printk("enter btn_open!\n");

	return 0;
}

ssize_t btn_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
	if(size!=1)
		return -EINVAL;

	//阻塞等待按键事件
	if(wait_event_interruptible(wqh, flag==1))
		return -EINTR;//被信号打断

	//上报键值和状态
	if(copy_to_user(buf, &key, size))
		return -EFAULT;

	//上报完数据flag清0
	flag = 0;
	
	return size;
}

int btn_release(struct inode *inode, struct file *filp)
{
	printk("enter btn_release!\n");

	return 0;
}

//声明操作函数集合
struct file_operations btn_fops = {
	.owner = THIS_MODULE,
	.open = btn_open,
	.read = btn_read,
	.release = btn_release,//对应用户close接口
};

//分配初始化miscdevice
struct miscdevice btn_dev = {
	.minor = MISC_DYNAMIC_MINOR,//系统分配次设备号
	.name = "btn",//设备文件名
	.fops = &btn_fops,//操作函数集合
};

//超时处理函数--- 真实按键事件
void btn_timer_function(unsigned long data)
{
	struct btn_dest *pdata = (struct btn_dest *)data;//引脚数据
	
	//区分按下松开
	//设置键值和状态
	key = pdata->code|gpio_get_value(pdata->gpio);

	flag = 1;
	
	//唤醒睡眠的进程
	wake_up_interruptible(&wqh);
}

//中断处理函数
irqreturn_t btn_handler(int irq, void *dev_id)
{
	//设置超时处理函数的参数
	btn_timer.data = (unsigned long)dev_id;
	//重置定时器10ms超时,用作按键消抖
	mod_timer(&btn_timer, jiffies+msecs_to_jiffies(10));
	
	return IRQ_HANDLED;//处理成功
}

//加载函数
int btn_init(void)
{
	int ret,i,j;

	//注册miscdevice
	ret = misc_register(&btn_dev);
	printk("BTN\tinitialized\n");

	//申请中断
	for(i=0;i<ARRAY_SIZE(btn_info);i++){
		//申请中断
		ret = request_irq(gpio_to_irq(btn_info[i].gpio), //中断号
						btn_handler, //中断处理函数
						IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, //中断标志,包括触发方式----- 上升下降沿触发
						btn_info[i].name, //中断名称
						&btn_info[i]);//传递给中断处理函数的参数
		if(ret<0){
			printk("request_irq failed!\n");
			goto failure_request_irq;
		}
	}

	//初始化等待队列
	init_waitqueue_head(&wqh);

	//初始化定时器
	init_timer(&btn_timer);
	btn_timer.function = btn_timer_function;

	printk("btn init!\n");

	return 0;

failure_request_irq:
	for(j=0;j<i;j++){
		free_irq(gpio_to_irq(btn_info[j].gpio), &btn_info[j]);
	}
	misc_deregister(&btn_dev);
	return ret;
}

//卸载函数
void btn_exit(void)
{
	int i;

	del_timer(&btn_timer);
	
	//释放所有申请的中断
	for(i=0;i<ARRAY_SIZE(btn_info);i++){
		free_irq(gpio_to_irq(btn_info[i].gpio), &btn_info[i]);
	}

	//注销miscdevice	
	misc_deregister(&btn_dev);
}

//声明为模块的入口和出口
module_init(btn_init);
module_exit(btn_exit);


MODULE_LICENSE("GPL");//GPL模块许可证
MODULE_AUTHOR("xin");//作者
MODULE_VERSION("2.0");//版本
MODULE_DESCRIPTION("btn driver!");//描述信息

 应用测试程序。

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

int main()
{
	int ret;
	char key = 0;

	int fd = open("/dev/btn",O_RDWR);
	if(fd==-1){
		perror("open");
		exit(-1);
	}

	printf("open successed!fd = %d\n",fd);

	while(1){
		ret = read(fd,&key,sizeof(key));
		if(ret<0){
			perror("read");
			break;
		}
		printf("key =  %#x\n",key);
	}

	close(fd);
	return 0;
}

 

 

最后,这只是等待队列的简单应用,实际应用中,不可能队列中就一个事件被阻塞。而且等待队列还有许多的函数可以调用,像添加队列成员和删除队列成员等等。以后有时间把等待队列的核心弄清楚了在做补充。

可以看看这篇博客(等待队列),比较详细的介绍了等待队列。好了,有什么疑问和建议欢迎在评论区中提出来喔。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值