Linux嵌入式驱动开发15——等待队列和工作队列

全系列传送门

Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

Linux嵌入式驱动开发02——驱动编译到内核

Linux嵌入式驱动开发03——杂项设备驱动(附源码)

Linux嵌入式驱动开发04——应用层和内核层数据传输

Linux嵌入式驱动开发05——物理地址到虚拟地址映射

Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写

Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)

Linux嵌入式驱动开发08——字符设备(步步为营)

Linux嵌入式驱动开发09——平台总线详解及实战

Linux嵌入式驱动开发10——设备树开发详解

Linux嵌入式驱动开发11——平台总线模型修改为设备树实例

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

Linux嵌入式驱动开发15——等待队列和工作队列

Linux嵌入式驱动开发16——按键消抖实验(内核定时器)

Linux嵌入式驱动开发17——输入子系统

Linux嵌入式驱动开发18——I2C通信

等待队列

阻塞与非阻塞

在这里插入图片描述

等待队列基础知识

在这里插入图片描述
所以用在中断中,我们如果有一个应用场景,就是app程序一直在运行,但是我们不希望一直在执行,就可以只有在我们按键后,才会相应执行的操作,所以使用等待队列的知识来进行程序设计。让app程序进入等待队列,以免app一直运行,占用了大部分的资源导致其他程序无法正常的使用。

等待队列头

在这里插入图片描述
在这里插入图片描述

等待队列相关函数

init_waitqueue_head宏

在这里插入图片描述

wait_event宏

在这里插入图片描述

wait_event_interruptible宏

在这里插入图片描述
在这里插入图片描述

wake_up宏

在这里插入图片描述

wake_up_interruptible宏

在这里插入图片描述

等待队列示例代码

分析

我们现在的app程序,使用read函数,不断地读取底层的value数据,会导致占用率过高,影响其他的程序工作,实际上我们只需要在按键执行后才可以读取到value的有效值
在这里插入图片描述
所以,使用等待队列,把read函数的操作挂起,然后当发生按键中断的时候,我们再唤醒传递value的数值

代码编写

在编译完成代码之后,运行我们的驱动模块,报错
在这里插入图片描述
通过对报错信息的分析,可以看到是杂项设备注册后面的程序出现了问题,经过排查,发现了两个错误

第一个就是与设备节点的compatible属性匹配的问题,一定要一致,所以这里要修改成{.compatible = "keys"},
在这里插入图片描述
第二个就是注册了杂项设备之后,一定要在注销驱动的函数里面注销杂项设备
在这里插入图片描述

验证

排查完错误,安装我们的模块,可以发现一切正常

接下来就是运行我们的app文件,运行后就可以发现这次卡在了驱动程序中的misc_read函数中,柱塞住了,只有按下按键的时候,才会执行操作,并且返回value的值

在这里插入图片描述

这里出现了一个小问题,就是在做实验时时候,需要反复的调整代码,然后发现执行了app程序之后关不掉了,因此也没有办法卸载driver.ko模块,所以,这里需要一个总结指令killall + 程序名称

killall app

然后就可以继续实验了

我们把打印信息都去除掉以后,再来使用

top

在这里插入图片描述
可以发现,我们的app程序,占用的资源瞬间降了下来。

到这里,我们就完成了,只有按键操作,才会执行app的read操作。

源码

driver.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>

#include <linux/wait.h>
#include <linux/sched.h>

#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>


struct device_node  *test_device_node;
struct tasklet_struct test_key_tasklet_struct;

DECLARE_WAIT_QUEUE_HEAD(key_wq);                // 定义并初始化等待队列头

int gpio_name;                                                                  // gpio编号
int irq;                                                                        // 中断号
int value = 0;                                                                  // 模拟管脚的状态
int wq_flags = 0;                                                               // 中断标志位


/*中断上文*/
irq_handler_t test_key_handle(int irq, void *args)                              // 中断处理函数
{
    value = !value;                                                             // 模拟中断处理函数一直在监测
    wq_flags = 1;                                                               // 中断被触发,说明按键按下了
    /*唤醒睡眠进程*/
    wake_up(&key_wq);

    return IRQ_HANDLED;                                                         // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}

int misc_open (struct inode *inode, struct file *file){                                             // 对底层操作一般就是通过应用层使用open read等函数时来实现
    
    printk("hello misc_open!!!\n");
    return 0;
}

int misc_release(struct inode *inode, struct file *file){

    printk("bye bye misc_release!!!\n");

    return 0;

}

/*底层数据传到应用层*/
ssize_t misc_read(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    
    // char kbuf[64] = "copy to user!!!\n";
    wait_event_interruptible(key_wq, wq_flags);                                                               // 先让这个读进入等待队列 wq_flags是等待的标志位

    if( copy_to_user(ubuf, &value, sizeof(value)) != 0 ){                                                     // 底层数据传递给应用层的ubuf
        printk("copy_to_user error!!!\n");
        return -1;
    }

    wq_flags = 0;                                                                                             // 标志位清零
    return 0;
}

/*应用层传入的数据*/
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    
    char kbuf[64] = {0};
    printk("hello misc_write!!!\n");

    if( copy_from_user(kbuf, ubuf, size) != 0 ){                                                            // 应用层传递过来的数据到底层的kbuf
        printk("copy_from_user error!!!\n");
        return -1;
    }
    printk("buf is:%s\n", kbuf);

/*********实现IO的逻辑功能*********************************************************************/
    if(kbuf[0] == 1){                                                                               //对蜂鸣器的控制,如果是1,控制gpio口
        // *vir_gpio5_dr |= (1 << 1);                                                               //因为是gpio5的01,所以左移一位就可以,给一个高电平,使蜂鸣器工作
        gpio_set_value(gpio_name, 1);                                                               //Linux体现出来了通用性,不需要寄存器地址了
        printk("gpio_set_value is high!!!\n");
    }else if(kbuf[0] == 0){
        // *vir_gpio5_dr &= ~(1 << 1); 
        gpio_set_value(gpio_name, 0);   
        printk("gpio_set_value is low!!!\n");             
    }

    return 0;
}

/*构建杂项设备驱动的file_operations*/
struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
};


/*杂项设备信息*/
struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,                // 次设备号
    .name = "hello_misc",                       // 生成一个名字叫做hello_misc的设备节点
    .fops = &misc_fops                          // 完善file_operations
};


/*probe函数*/
int beep_probe(struct platform_device *pdev){
    
    int ret = 0;

    printk("beep_probe ok!!!\n");
    
    /*间接获取设备节点信息*/
    /********查找指定路径的节点***********/
    test_device_node = of_find_node_by_path("/test_key");               // 节点名字叫做test_key
    if(test_device_node == NULL) {
        printk("of_find_node_by_path error!!!\n");
        return -1;
    }else {
        printk("of_find_node_by_path ok!!!\n");
        printk("test_device_node name is %s\n", test_device_node->name);
    }

    gpio_name = of_get_named_gpio(test_device_node, "gpios", 0);         //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;
    if(gpio_name < 0) {
        printk("of_get_named_gpio error!!!\n");
        return -1;
    }else{
        printk("of_get_named_gpio ok!!!\n");
    }
    
    /*gpio子系统*/
    gpio_direction_input(gpio_name);                                    // 因为是模拟按键,方向设置成输入模式
   
    // irq = gpio_to_irq(gpio_name);                                       // 通过gpio函数获取中断号,参数是gpio编号
   
    /* irq_of_parse_and_map,通过设备树中interrupts获取中断号
     * 第一个参数:设备树节点
     * 第二个参数:索引值,这里只有一个,所以是0
     * */
    irq = irq_of_parse_and_map(test_device_node, 0);
   
    printk("irq is %d\n", irq);

    /* request_irq申请中断
     * 第一个参数:中断号,
     * 第二个参数:中断处理函数,
     * 第三个参数:中断标志(边沿触发方式),
     * 第四个函数:中断名字,
     * 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数
     */
    ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL);  
    if(ret < 0) {
        printk("request_irq failed!!!\n");
        return -1;
    }else{
        printk("request_irq successful!!!\n");
    }

    /*注册杂项设备,生成dev驱动节点*/
    ret = misc_register(&misc_dev);
    if(ret < 0){
        printk("misc_register failed!!!\n");
        return -1;
    }else{
        printk("misc_register succeed!!!\n"); 
    }
    
    return 0;
}

int beep_remove(struct platform_device *pdev){
    printk("beep_remove ok!!!\n");
    return 0;
}

const struct platform_device_id beep_id_table = {
    .name = "keys",
};

const struct of_device_id of_match_table_test[] = {
    {.compatible = "keys"},                         // 匹配设备树节点中的compatible属性信息
    {}                                              // 不写会提示警告
};

struct platform_driver beep_device = {
    .probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    .remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    .driver = {
        .owner = THIS_MODULE,      
        .name  = "keys",
        .of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name
    },                                              //   内置的device_driver 结构体
    .id_table = &beep_id_table,
};

/*驱动初始化*/
static int beep_driver_init(void)
{
    int ret = 0;
    printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printf
    
    ret = platform_driver_register(&beep_device);

    if(ret < 0){
        printk("platform_driver_register error!!!\n");
        return ret;
    }else{
        printk("platform_driver_register ok!!!\n");
    }
    
    return 0;
}

static void beep_driver_exit(void)
{
    printk("beep_driver_exit bye!!!\n");

    free_irq(irq, NULL);
    tasklet_kill(&test_key_tasklet_struct);
    misc_deregister(&misc_dev);

    platform_driver_unregister(&beep_device);
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

app.c

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

#include <sys/ioctl.h>



int main(int argc, char *argv[])
{
    int fd;

    int value;

    fd = open("/dev/hello_misc", O_RDWR);       // 打开节点时候触发open函数

    if(fd < 0){

        perror("open error\n");                 // perror在应用中打印
        return fd;
    }
    while(1){
        read(fd, &value, sizeof(value));
        // printf("value is %d\n", value);
    }

    return 0;
}

工作队列

什么是工作队列

在这里插入图片描述

工作队列的工作原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

工作队列相关API

在这里插入图片描述
跟等待队列一样,先定义结构体,然后初始化

DECLARE_WORK宏

在这里插入图片描述

INIT_WORK宏

在这里插入图片描述
在这里插入图片描述

schedule_work

在这里插入图片描述

工作队列示例代码

分析

这里修改之前的等待队列的代码,把中断下文设置成工作队列的模式。

当检测到按键中断之后,才会触发工作队列中的函数。

代码

这里是很简单的,相比之前的代码,只需要替换掉tasklet相应的函数即可。

  • 首先就是初始化INIT_WORK

  • 然后编写工作队列函数

  • 最后在中断中调度工作队列

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>

struct device_node  *test_device_node;

// struct tasklet_struct test_key_tasklet_struct;
struct work_struct test_key_tasklet_struct;

int gpio_name;                                                                  // gpio编号
int irq;                                                                        // 中断号

/*打印一百次,模拟一个较长实践的任务*/
void test(unsigned long data)
{
    int i = 100;
    while(i--){
        printk("test_key is %d\n",i);
    }
}

/*中断上文*/
irq_handler_t test_key_handle(int irq, void *args)                              // 中断处理函数
{
    printk("start\n");

    /*启动中断下文*/
    // tasklet_schedule(&test_key_tasklet_struct);

    /*调度工作队列*/
    schedule_work(&test_key_tasklet_struct);

    printk("schedule_work end!!!\n");

    return IRQ_HANDLED;                                                         // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}


/*probe函数*/
int beep_probe(struct platform_device *pdev){
    
    int ret = 0;

    printk("beep_probe ok!!!\n");
    
    /*间接获取设备节点信息*/
    /********查找指定路径的节点***********/
    test_device_node = of_find_node_by_path("/test_key");               // 节点名字叫做test_key
    if(test_device_node == NULL) {
        printk("of_find_node_by_path error!!!\n");
        return -1;
    }else {
        printk("of_find_node_by_path ok!!!\n");
        printk("test_device_node name is %s\n", test_device_node->name);
    }

    gpio_name = of_get_named_gpio(test_device_node, "gpios", 0);         //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;
    if(gpio_name < 0) {
        printk("of_get_named_gpio error!!!\n");
        return -1;
    }else{
        printk("of_get_named_gpio ok!!!\n");
    }
    
    gpio_direction_input(gpio_name);                                    // 因为是模拟按键,方向设置成输入模式
   
    // irq = gpio_to_irq(gpio_name);                                       // 通过gpio函数获取中断号,参数是gpio编号
   
    /* irq_of_parse_and_map,通过设备树中interrupts获取中断号
     * 第一个参数:设备树节点
     * 第二个参数:索引值,这里只有一个,所以是0
     * */
    irq = irq_of_parse_and_map(test_device_node, 0);
   
    printk("irq is %d\n", irq);

    /* request_irq申请中断
     * 第一个参数:中断号,
     * 第二个参数:中断处理函数,
     * 第三个参数:中断标志(边沿触发方式),
     * 第四个函数:中断名字,
     * 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数
     */
    ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL);  
    if(ret < 0) {
        printk("request_irq failed!!!\n");
        return -1;
    }else{
        printk("request_irq successful!!!\n");
    }

    /* tasklet_init 初始化
     * 第一个参数:tasklet_struct结构体
     * 第二个参数:中断下文执行的函数
     * 第三个参数:传递给中断下文的参数
     */
    // tasklet_init(&test_key_tasklet_struct, test, 100);

    /* INIT_WORK 初始化
     * 第一个参数:work_struct结构体
     * 第二个参数:工作队列执行函数
     */
    INIT_WORK(&test_key_tasklet_struct, test);
    
    return 0;
}

const struct platform_device_id beep_id_table = {
    .name = "keys",
};

int beep_remove(struct platform_device *pdev){
    printk("beep_remove ok!!!\n");
    return 0;
}

const struct of_device_id of_match_table_test[] = {
    {.compatible = "keys"},
    {}                                              // 不写会提示警告
};

struct platform_driver beep_device = {
    .probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个
    .remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
    .driver = {
        .owner = THIS_MODULE,      
        .name  = "keys",
        .of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name
    },                                              //   内置的device_driver 结构体
    .id_table = &beep_id_table,
};

static int beep_driver_init(void)
{
    int ret = 0;
    printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printf
    
    ret = platform_driver_register(&beep_device);

    if(ret < 0){
        printk("platform_driver_register error!!!\n");
        return ret;
    }else{
        printk("platform_driver_register ok!!!\n");
    }
    
    return 0;
}

static void beep_driver_exit(void)
{
    printk("beep_driver_exit bye!!!\n");

    free_irq(irq, NULL);
    // tasklet_kill(&test_key_tasklet_struct);

    platform_driver_unregister(&beep_device);
}

module_init(beep_driver_init);
module_exit(beep_driver_exit);


MODULE_LICENSE("GPL");              //声明模块拥有开源许可

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值