嵌入式LINUX驱动学习之9等待队列
一、头文件、函数及说明
/*
wait_queue_head_t 结构体
源码位置:include/linux/wait.h
*/
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
/*
初始化等待队列头 :init_waitqueue_head(wait_queue_head_t *q);
源码位置:include/linux/wait.h
*/
extern void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *);
#define init_waitqueue_head(q) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((q), #q, &__key); \
} while (0)
/*
进行等待队列 :wait_event(wait_queue_head_t wq,int condition)
功能:进行不可中断的休眠操作,底层实现调用了衍生自旋锁
源码位置:include/linux/wait.h
*/
#define wait_event(wq, condition) \
do { \
if (condition)/*当condition为真,不会进行休眠*/ \
break; \
__wait_event(wq, condition); \
} while (0)
/*
进行等待队列 :wait_event_interruptible(wait_queue_head_t wq,int condition)
功能:进行可中断的休眠操作,
源码位置:include/linux/wait.h
*/
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \
__wait_event_interruptible(wq, condition, __ret); \
__ret; \
})
/*
唤醒等待队列的方法:wake_up(wait_queue_head_t *wq)
功能,唤醒等待队列所有的进程
源码位置:include/linux/wait.h
*/
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr, void *key);
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
/*
唤醒等待队列的方法: wake_up_interruptible()
功能,唤醒等待队列可中断的进程
源码位置:include/linux/wait.h
*/
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
二、代码举例(内核空间)
功能:
实现只能同时一个用户打开字符设备文件,即:只能同时一个用户操作硬件设备
通过按键控制LED灯的开和关,用户空间程序可以显示按键操作信号
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <cfg_type.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
/*定义硬件信号*/
struct dev_src {
char *btn_name;
int btn_gpio;
char *led_name;
int led_gpio;
};
struct dev_src dev_info[] = {
{
.btn_name = "BTN_1",
.btn_gpio = PAD_GPIO_A + 28,
.led_name = "LED_1",
.led_gpio = PAD_GPIO_B + 26
},
{
.btn_name = "BTN_2",
.btn_gpio = PAD_GPIO_B + 30,
.led_name = "LED_2",
.led_gpio = PAD_GPIO_C + 11
},
{
.btn_name = "BTN_3",
.btn_gpio = PAD_GPIO_B + 31,
.led_name = "LED_3",
.led_gpio = PAD_GPIO_C + 7
},
{
.btn_name = "BTN_4",
.btn_gpio = PAD_GPIO_B + 9,
.led_name = "LED_4",
.led_gpio = PAD_GPIO_C + 12
}
};
/*通过等待队列实只能一个用户操作硬件设备*/
static int fops_couter = 1;
static wait_queue_head_t wqt;
/*通过等待队列实现LED灯状态显示*/
static int led_counter = 0;//当有按键操作时,变量在0/1变动,
static wait_queue_head_t led_wqt;
static unsigned long g_led_buf[3];//保存要拷贝到用户空间的信息:中断号、LED灯编号 、LED灯状态
static struct timer_list s_timer;//通过软件定时器给按键去抖动
static int f_open(struct inode * inode , struct file * file){
printk("文件准备打开,有可能进入休眠等待!\n");
wait_event(wqt,fops_couter);//第一个用户可以正常打开,后续用户只能进入等待队列,因为后续fops_couter =0;
fops_couter = 0;
printk("文件打开完成\n");
return 0;
};
static int f_close(struct inode * inode ,struct file * file){
printk("文件准备关闭,并唤醒等待队列其它进程\n");
/*关闭字符设备文件,唤醒等待队列其它进程,并将fops_couter置1;*/
wake_up(&wqt);
fops_couter =1 ;
return 0;
}
/*当有按键操作时,进程被唤醒,拷贝内核空间信息到用户空间,并执行led_counter置0*/
static long f_ioctl(struct file * file , unsigned int cmd , unsigned long ubuf){
/*当led_counter ==1时,可以向下执行,否则进行休眠等待,但可以因超时或中断而结束*/
wait_event_interruptible(led_wqt,led_counter);//
copy_to_user((unsigned long *)ubuf,g_led_buf,sizeof(int) * 3);
led_counter = 0;
return 0;
}
struct file_operations f_ops = {
.owner = THIS_MODULE,
.open = f_open,
.release = f_close,
.unlocked_ioctl = f_ioctl
};
struct miscdevice misc_ops = {
.minor = MISC_DYNAMIC_MINOR,
.name = "file_drv",
.fops = &f_ops
};
static irqreturn_t btn_irq(int irq,void * args){
struct dev_src *led_info =(struct dev_src *) args;
//保存中断号
g_led_buf[0] = irq;
//保存LED灯编号
g_led_buf[1] = led_info -> led_name[strlen(led_info ->\
led_name) -1] - '0';
//保存ELD灯状态
g_led_buf[2] = 1 - gpio_get_value(led_info ->led_gpio);
gpio_set_value(led_info -> led_gpio,g_led_buf[2]);//设置LED灯状态
mod_timer(&s_timer, msecs_to_jiffies(10) + jiffies);//重量置软件定时器,用于按键去抖动 ;//也可以考虑使用延迟函数mdelay()
return IRQ_HANDLED;
}
/*软件定时器超时处理函数*/
static void btn_timer_func(unsigned long data){
led_counter =1;
wake_up_interruptible(&led_wqt);//唤醒可中断的等待队列中的进程
}
static int mywait_init(void){
/*申请GPIO资源、设置GPIO功能、申请irq*/
int i = 0;
for(; i <ARRAY_SIZE(dev_info); i ++){
gpio_request(dev_info[i].btn_gpio,dev_info[i].btn_name);
gpio_direction_input(dev_info[i].btn_gpio);
request_irq(gpio_to_irq(dev_info[i].btn_gpio),btn_irq,\
IRQF_TRIGGER_RISING,dev_info[i].btn_name,\
&dev_info[i]);
gpio_request(dev_info[i].led_gpio,dev_info[i].led_name);
gpio_direction_output(dev_info[i].led_gpio,1);
}
misc_register(&misc_ops);//注册混杂设备对象
init_timer(&s_timer);//初始化定时器
s_timer.function = btn_timer_func;//指定软件定时器超时处理函数
init_waitqueue_head(&wqt);//初始化,不能中断的等待队列,用于字符设备文件打开的关闭
init_waitqueue_head(&led_wqt);//初始化,能中断的等待队列,用于LED灯
return 0;
}
static void mywait_exit(void){
int i = 0;
/*释放GPIO资源,释放IRQ*/
for(; i<ARRAY_SIZE(dev_info);i++){
free_irq(gpio_to_irq(dev_info[i].btn_gpio),&dev_info[i]);
gpio_free(dev_info[i].btn_gpio);
gpio_set_value(dev_info[i].led_gpio,1);
gpio_free(dev_info[i].led_gpio);
}
del_timer(&s_timer);//删除定器对象
misc_deregister(&misc_ops);//卸载混杂设备对象
}
module_init(mywait_init);
module_exit(mywait_exit);
MODULE_LICENSE("GPL");
三、代码举例(用户空间)
功能:
当内核空间发送数据过来,执行打印,其它时间死等
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define F_R 0X1000
static void r_prinf(int ubuf[3]){
printf("中断信号:%d,LED_%d灯响应,执行动作%s,LED灯状态%s\n",\
ubuf[0],ubuf[1],ubuf[2] ? "关灯" : "开灯",\
ubuf[2] ? "关闭" : "开启");
}
int main(int argc ,char * argv[]){
if(argc != 2)
goto err;
int ubuf[3];
int fp = open(argv[1],O_RDWR);
if(fp < 0)
printf("文件打开失败\n");
for(;;){
ioctl(fp,F_R,ubuf);
r_prinf(ubuf);
}
close(fp);
return 0;
err :
printf("命令错误!\n");
printf(" comm <cdev_file>\n");
return -1;
}