嵌入式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;
}

猜你喜欢

转载自blog.csdn.net/weixin_47273317/article/details/108106056