当前位置: 首页 > article >正文

Linux驱动开发-①中断②阻塞、非阻塞IO和异步通知

Linux驱动开发-①中断②阻塞、非阻塞IO和异步通知

  • 一,中断
    • 1.中断的流程
    • 2.上半部和下半部
      • 2.1上半部
      • 2.2下半部
        • 2.2.1 tasklet
        • 2.2.2 工作队列
    • 3.按键延时消抖中断程序
  • 二,阻塞和非阻塞IO和异步通知
    • 1.阻塞IO
      • 1.1 常见结构1
      • 1.2 常见结构2
    • 2.非阻塞IO
      • 2.1 驱动结构
      • 2.2 select 应用程序
      • 2.3 poll 应用程序
    • 3.异步通知

一,中断

1.中断的流程

  ①获取中断号以及中断名②申请中断③释放中断

/*中断结构体*/
struct irq_key{
    int key_gpio;     //io编号
    int irq;  //中断号
    unsigned char gpio_name[6];//设置中断名
    irqreturn_t (*handler)(int, void *);
};
	static irqreturn_t key_irq_hander(int irq, void *dev_id)//中断处理函数,遇到上升沿或者下降沿触发定时器
	{
		..............
	}
	
    /*中断设置*/
    key.irq_struct.irq = gpio_to_irq(key.irq_struct.key_gpio);//获取中断号
    key.irq_struct.handler = key_irq_hander;//中断函数表注册,即中断函数
    ret = request_irq(key.irq_struct.irq,key.irq_struct.handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, key.irq_struct.gpio_name, &key);
    
    /*释放中断*/
     free_irq(key.irq_struct.irq, &key);

2.上半部和下半部

2.1上半部

  上半部执行的是紧急的,不可延迟的任务。当中断上半部执行时,当前CPU上的其他程序包括用户空间程序和内核线程都会暂时阻塞,直到中断处理完成
  不可抢占:在中断上下文中,当前 CPU 会禁用抢占,直到中断处理完成。不能睡眠:中断上下文中不能调用可能睡眠的函数(如 kmalloc(GFP_KERNEL)、mutex_lock 等)。高优先级:中断处理程序的优先级高于普通进程和内核线程。

2.2下半部

  下半部处理非紧急的,耗时的任务,将复杂的任务推迟到合适的时机,避免阻塞中断上半部。可以运行在进程上下文中,睡眠或者调用睡眠的函数,能被其他中断或者是任务抢占。用的tasklet(软中断,依旧不能睡眠,优先级依旧高,适用于网络数据包处理,块设备处理)和工作队列(可以睡眠,比较耗时的任务,比如访问系统文件,分配内存)。

2.2.1 tasklet

  tasklet跟工作队列实际使用差不多,tasklet他的处理函数传递参数,工作队列不传递。这个在初始化的时候也能发现,tasklet函数初始化需要三个参数,包括传递的设备参数结构体,工作队列初始化只需要两个参数。

struct tasklet_struct tasklet;
/*tasklet*/
static void tasklet_func(unsigned long data)//中断下半部tasklet函数
{
........
}
static irqreturn_t key_irq_hander(int irq, void *dev_id)//中断处理函数
{
   struct key_dev *key_irq = (struct key_dev*)dev_id;
   tasklet_schedule(&key_irq->irq_struct.tasklet);//调用中断下半部
    return IRQ_HANDLED;
}

/*初始化*/	
tasklet_init(&key.irq_struct.tasklet,tasklet_func, (unsigned long)&key);
2.2.2 工作队列
 struct work_struct testwork; //工作队列
/*工作队列*/
void testwork_func_t(struct work_struct *work)//中断下半部
{
........
}
static irqreturn_t key_irq_hander(int irq, void *dev_id)//中断处理函数
{
   struct key_dev *key_irq = (struct key_dev*)dev_id;
   schedule_work(&key_irq->irq_struct.testwork);//工作队列
    return IRQ_HANDLED;
}

/*初始化*/	
INIT_WORK(&key.irq_struct.testwork,testwork_func_t);

3.按键延时消抖中断程序

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>  
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/ioctl.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/string.h>
#define KEY_NAME "key_irq"
#define KEY_EN_VALUE  0X01
#define KEY_NO_VALUE  0X00
static int key_pan=0;
/*中断结构体*/
struct irq_key{
    int key_gpio;     //io编号
    int irq;  //中断号
    unsigned char value; //按键值
    unsigned char gpio_name[6];//设置按键名
    irqreturn_t (*handler)(int, void *);
    struct tasklet_struct tasklet;//tasklet
    struct work_struct testwork; //工作队列

};
struct key_dev{
    struct class *class;
    struct device *device;
    struct device_node *nd;
    struct cdev cdev;
    dev_t key_hao;
    int major;  //主设备号
    int minor;  //次设备号

    struct irq_key irq_struct;/*中断结构体*/



    int timer_period;//定时器周期  利用原子操作保护
    atomic_t lock_yuan;//原子操作
    struct timer_list timer;//定义定时器  
};
struct key_dev key;
static int pgkey_open(struct inode *innode,struct file *filp)
{
    filp->private_data = &key;//将key结构体数据设为私有数据
    return 0;
}

static int pgkey_release(struct inode *innode,struct file *filp)
{
    return 0;
}
static ssize_t key_read(struct file *filp,  char __user *buf, size_t count, loff_t *ppos)
{
    static int key_value = 0,ret = 0;
    struct key_dev *key_read =(struct key_dev *)filp->private_data;
    key_value = key_read->irq_struct.value; 
    if(key_pan)//按下
    {
        ret = __copy_to_user(buf, &key_value, sizeof(key_value));
        if(ret<0)
        {
            printk("key value read error!\r\n");
            return -1;
        }        
    }
     key_pan = 0;
    return 0;

}
static struct file_operations pgkey_fops={
    .owner=THIS_MODULE,
    .open=pgkey_open,
    .read=key_read,
    .release=pgkey_release,

};
static void key_timer_function(unsigned long arg)//定时结束后会执行第操作,
{
    static int ret = 0;
    struct key_dev *key_t = (struct key_dev*)arg;
    ret = gpio_get_value(key_t->irq_struct.key_gpio);
    if(ret==0)
    {
        printk("KEY PUSH\r\n");
        key_t->irq_struct.value = KEY_EN_VALUE;
         key_pan = 1;
        
    }else 
    {
        printk("KEY PULL\r\n");
        key_t->irq_struct.value = KEY_NO_VALUE;
       
    }
}

/*工作对列*/
void testwork_func_t(struct work_struct *work)//工作队列和tasklet函数使用不同,ta这个函数通过本身传递参数,工作队列不传参数进来.
{
    // struct key_dev *key_work = (struct key_dev*)work;
    key.timer.data = (unsigned long)&key;
    mod_timer(&key.timer, jiffies+msecs_to_jiffies(15));//添加定时器,并且设置定时器时间
    printk("key_work !!!\r\n");
}

// /*tasklet*/
// static void tasklet_func(unsigned long data)//中断下半部tasklet函数
// {
//     struct key_dev *key_task = (struct key_dev*)data;
//     key_task->timer.data = data;
//     mod_timer(&key_task->timer, jiffies+msecs_to_jiffies(15));//添加定时器,并且设置定时器时间
//     printk("tasklet !!!\r\n");
// }
static irqreturn_t key_irq_hander(int irq, void *dev_id)//中断处理函数,遇到上升沿或者下降沿触发定时器
{
    struct key_dev *key_irq = (struct key_dev*)dev_id;

    schedule_work(&key_irq->irq_struct.testwork);//工作队列
    // tasklet_schedule(&key_irq->irq_struct.tasklet);//调用中断下半部函数
    // key_irq->timer.data = (unsigned long)dev_id;
    // mod_timer(&key_irq->timer, jiffies+msecs_to_jiffies(15));//添加定时器,并且设置定时器时间
    return IRQ_HANDLED;
}

static void gpio_init(void)
{
    int ret =0;
    // atomic_set(&key.lock_yuan, key.timer_period);
    key.nd = of_find_node_by_path("/key");//在设备树中获取节点
    key.irq_struct.key_gpio = of_get_named_gpio(key.nd,"key-gpio", 0);//得到gpio引脚
    ret = gpio_request(key.irq_struct.key_gpio,KEY_NAME);
    if(ret<0)
    {
        printk("gpio requst error !\r\n");
    }
    ret = gpio_direction_input(key.irq_struct.key_gpio);//设置输入
    if(ret == 0)
    { 
        printk("从设备树读取节点和gpio正确\r\n");
    }else {
        gpio_free(key.irq_struct.key_gpio);
    }

    /*中断设置*/
    key.irq_struct.irq = gpio_to_irq(key.irq_struct.key_gpio);//获取中断号
    key.irq_struct.handler = key_irq_hander;//中断函数表注册
    key.irq_struct.value = KEY_EN_VALUE;//按键值
    //key.irq_struct.tasklet = tasklet_func;//添加下半部函数tasklet
    memset(key.irq_struct.gpio_name,0,sizeof(key.irq_struct.gpio_name));
    strncpy(key.irq_struct.gpio_name, "key0", sizeof(key.irq_struct.gpio_name));//按键名
    // tasklet_init(&key.irq_struct.tasklet,tasklet_func, (unsigned long)&key);
    INIT_WORK(&key.irq_struct.testwork,testwork_func_t);
    ret = request_irq(key.irq_struct.irq,key.irq_struct.handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                        key.irq_struct.gpio_name, &key);
    if(ret<0)
    {
         printk("irq requset error !\r\n");
    }
}

static int __init pgkey_init(void)
{
    gpio_init();//gpio初始化

    /*注册*/
    /*1.设备号*/
    if(key.major)
    {
        key.key_hao = MKDEV(key.major,0);
        register_chrdev_region(key.key_hao, 1, KEY_NAME);//主动注册
    }else{
        alloc_chrdev_region(&key.key_hao, 0, 1, KEY_NAME);//自动注册
    }
    printk("major = %d,minor = %d",MAJOR(key.key_hao),MINOR(key.key_hao));

    /*2.注册函数*/
    key.cdev.owner = THIS_MODULE;
    cdev_init(&key.cdev,&pgkey_fops);
    cdev_add(&key.cdev,key.key_hao,1);

    /*3.节点申请*/ 
    key.class = class_create(THIS_MODULE,KEY_NAME);
    key.device = device_create(key.class, NULL,key.key_hao, NULL,KEY_NAME);

    /*初始化定时器*/
    init_timer(&key.timer);//初始化定时器
    key.timer.function = key_timer_function;//定时器函数
    return 0;
}

static void  __exit pgkey_exit(void)
{
    
    free_irq(key.irq_struct.irq, &key);
    del_timer(&key.timer);
    gpio_free(key.irq_struct.key_gpio);


    cdev_del(&key.cdev);//先删除设备
    unregister_chrdev_region(key.key_hao,1);//删除设备号
    device_destroy(key.class,key.key_hao);//先删除和设备第关系
    class_destroy(key.class);//再删除类
   
}


/*驱动入口和出口*/
module_init(pgkey_init);
module_exit(pgkey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyt");



二,阻塞和非阻塞IO和异步通知

在这里插入图片描述

1.阻塞IO

  这种思想就是让应用程序等一下,等好了,在运行。应用程序在对驱动程序操作时,如果不能获取到资源,阻塞IO会将应用程序休眠,线程挂起,直到能获取到资源时,再进行应用程序的操作。不然应用程序一直对按键进行查询,占用CPU率高达90%以上。

1.1 常见结构1

  利用函数 wait_event_interruptible,放在读函数中,只有在定时器函数中,判断出按键按下,才会唤醒,wake_up_interruptible唤醒等待队列中的线程,驱动函数 wait_event_interruptible下面的内容才能执行,从而应用读取到数据。

wait_queue_head_t r_wait;//等待队列头 
static ssize_t key_read(struct file *filp,  char __user *buf, size_t count, loff_t *ppos)
{
    static int key_value = 0,ret = 0;
    struct key_dev *key_read =(struct key_dev *)filp->private_data;
    wait_event_interruptible(key_read->r_wait,key_pan);//等待事件,当按键按下有效
   .........
    return 0;
}
static void key_timer_function(unsigned long arg)//定时结束后会执行第操作,
{
			........
    /*唤醒进程*/
    if(key_pan)
    {
        wake_up_interruptible(&key_t->r_wait);
    }
}
init_waitqueue_head(&key.r_wait);//初始化

1.2 常见结构2

  等待队列头是一个链表头,用于管理所有等待某个事件的线程,因为可能有好多文件都调用这个驱动模块。
  等待队列项表示一个正在等待某个事件的线程,表示某一个。

wait_queue_head_t r_wait;//等待队列头 
static ssize_t key_read(struct file *filp,  char __user *buf, size_t count, loff_t *ppos)
{
    static int key_value = 0,ret = 0;
    struct key_dev *key_read =(struct key_dev *)filp->private_data;
    DECLARE_WAITQUEUE(wait,current);//定义的等待队列
    add_wait_queue(&key_read->r_wait,&wait);//将队列添加到等待队列头
    __set_current_state(TASK_INTERRUPTIBLE);//设置为可被打断状态,即用kill -9能杀掉
    schedule();//让出 CPU,线程进入睡眠状态,等待被唤醒
    /*判断被其他信号唤醒*/
    if(signal_pending(current))
    {
        goto data_error;
    }
	......

 data_error:
    __set_current_state(TASK_RUNNING);//将当前任务设置为运行状态
    remove_wait_queue(&key_read->r_wait,&wait);//将对应的队列项从等待队列头删除
    return 0;
}
static void key_timer_function(unsigned long arg)//定时结束后会执行第操作,
{
			........
    /*唤醒进程*/
    if(key_pan)
    {
        wake_up_interruptible(&key_t->r_wait);
    }
}
init_waitqueue_head(&key.r_wait);//初始化

2.非阻塞IO

  这种思想是让应用程序每隔一定时间(很短)查一下,其他时间去干别的事情,当查询好了的话,就可以运行了。应用程序在对驱动程序操作时,如果不能获取到资源,非阻塞IO会一直轮询等待,设置间隔时间,比如200ms,每隔200ms就会去查询驱动能否获取数据。中间的时间,也就是这个200ms内进行其他操作。这个要在应用程序中,打开文档操作加上O_NONBLOCK: fd = open(filename,O_RDWR | O_NONBLOCK);//按照非阻塞方式打开

2.1 驱动结构

wait_queue_head_t r_wait;//等待队列头 
static ssize_t key_read(struct file *filp,  char __user *buf, size_t count, loff_t *ppos)
{
    static int key_value = 0,ret = 0;
    struct key_dev *key_read =(struct key_dev *)filp->private_data;
    if(filp->f_flags & O_NONBLOCK)//判断是阻塞还是非阻塞方式
    {
        if(key_pan==0)
        {
            return -EAGAIN;//非阻塞            
        }

    }else wait_event_interruptible(key_read->r_wait,key_pan);//否则按照阻塞方式
	......
}
static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{
    int ret = 0;
    struct key_dev *key_poll = (struct key_dev *)filp->private_data;
    poll_wait(filp, &key_poll->r_wait, wait);//文件,等待队列头,等待队列项
    if(key_pan)//poll判断的内容,按键按下可读
    {
        ret = POLLIN | POLLRDNORM;
    }
    return ret;
}
static struct file_operations pgkey_fops={
    .owner=THIS_MODULE,
    .open=pgkey_open,
    .read=key_read,
    .release=pgkey_release,
    .poll = key_poll,

};
init_waitqueue_head(&key.r_wait);//初始化

2.2 select 应用程序

  应用程序有两种,poll和select,后者能监视的文件数量有最大限制1024,poll函数没有最大文件限制。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
 #include "linux/ioctl.h"
int main(unsigned char argc,unsigned char *argv[])
{
    int rel = 0,fd = 0,arg = 0;
	int cmd = 0;
    struct pollfd fds;
    fd_set readfds;
    struct timeval timeout;    
    unsigned char *filename,value[1]={0};
    filename = argv[1];
    fd = open(filename,O_RDWR | O_NONBLOCK);//按照非阻塞方式打开
    if(fd<0) printf("open file error\r\n");
    
    /*select*/
    while(1)
    {
        FD_ZERO(&readfds);//清除
        FD_SET(fd,&readfds);//添加fd到readfds中
        timeout.tv_sec=0;
        timeout.tv_usec = 500000;//500ms
        rel = select(fd+1,&readfds,NULL,NULL,&timeout);
        switch (rel)
        {
        case 0:
            printf("time out !!\r\n");
            break;
        case -1:
            break;
        default:
            rel = read(fd,value,sizeof(value));
            if(rel<0)
            {
                
            }else{
                    if(value[0])
                    {
                        printf("value = %x\r\n",value[0]);
                    }          
            }
            break;
        }

    }   
    rel = close(fd);
    if(rel<0) printf("close in  APP error\r\n");
    return 0;
}

2.3 poll 应用程序

```c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
 #include "linux/ioctl.h"
int main(unsigned char argc,unsigned char *argv[])
{
    int rel = 0,fd = 0,arg = 0;
	int cmd = 0;
    struct pollfd fds;
    fd_set readfds;
    struct timeval timeout;    
    unsigned char *filename,value[1]={0};
    filename = argv[1];
    fd = open(filename,O_RDWR | O_NONBLOCK);//按照非阻塞方式打开
    if(fd<0) printf("open file error\r\n");
    fds.fd=fd;
    fds.events = POLLIN;
    while(1)
    {

        rel = poll(&fds,1,1000);
        switch (rel)
        {
        case 0:
            printf("time out !!\r\n");
            break;
        case -1:
            break;
        default:
            rel = read(fd,value,sizeof(value));
            if(rel<0)
            {
                
            }else{
                    if(value[0])
                    {
                        printf("value = %x\r\n",value[0]);
                    }          
            }
            break;
        }
    }
    rel = close(fd);
    if(rel<0) printf("close in  APP error\r\n");
    return 0;
}

3.异步通知

  类似于中断的思想,当驱动中判断出按键按下,发送一个信号,到应用对应的进程中,从而引起应用程序执行读取按键的操作,在应用程序中,类似于中断的方式。
驱动框架:

 struct fasync_struct *key_fasync;//异步通知
static void key_timer_function(unsigned long arg)//定时结束后会执行第操作,
{
	  .......
    if(key_pan)//按下
    {
        printk("in linux  key push  in fasync!\r\n");
        kill_fasync(&key_t->key_fasync,SIGIO,POLL_IN);//添加异步通知,发送这个信号
    }
}

static int key_file_fasync(int fd,struct file *filp,int on)
{
    struct key_dev *dev = (struct key_dev *)filp->private_data;
    return fasync_helper(fd, filp,on,&dev->key_fasync);
}
static int pgkey_release(struct inode *innode,struct file *filp)
{//这段还是之前的释放函数,不过添加异步通知后,在释放文件时,关闭异步通知。
    return key_file_fasync(-1,filp,0);
}
static struct file_operations pgkey_fops={
    .owner=THIS_MODULE,
    .open=pgkey_open,
    .read=key_read, 
    .fasync =key_file_fasync,
    .release=pgkey_release,
}; 

  应用程序:这个key_signal就类似于中断函数, signal(SIGIO,key_signal)相当于中断函数注册的意思,从而在应用程序中,将应用程序对应的进程号给内核,并且打开异步通知,内核中驱动程序判断按键按下后,将信号传递给这个应用程序进程,执行key_signal函数,读取按键值。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
#include "signal.h"
static int fd = 0;
static void key_signal(int num)
{
    printf("getin APP key_signal\r\n");
    static int ret =0;
    unsigned char value = 0;

    ret = read(fd,&value,sizeof(value));
    if(ret<0)
    {
         printf("read error!\r\n");
    }else 
    {
        printf("KEY = %d\r\n",value);
    }
}
int main(unsigned char argc,unsigned char *argv[])
{
    int rel = 0,arg = 0,flags = 0;
    struct pollfd fds;
    fd_set readfds;
    struct timeval timeout;    
    unsigned char *filename,value[1]={0};
    filename = argv[1];
    fd = open(filename,O_RDWR);//按照阻塞方式打开
    if(fd<0) printf("open file error\r\n");

    /*设置信号处理函数*/
    signal(SIGIO,key_signal);
    fcntl(fd,F_SETOWN,getpid());//将本应用的进程号告诉内核,从而内核传递的信号到这个进程
    flags = fcntl(fd,F_GETFD);//获取当前进程状态
    fcntl(fd,F_SETFL,flags | FASYNC);//开启当前进程异步通知
    printf("open fasync!\r\n");
    while(1)
    {
        sleep(2);
    }
    printf("OVER\r\n");
    rel = close(fd);
    if(rel<0) printf("close in  APP error\r\n");
    return 0;

}



http://www.kler.cn/a/595361.html

相关文章:

  • Python 爬取 1688 商品详情接口数据全攻略
  • iStoreOS软路由对硬盘格式化分区(转化ext4)
  • Java实现十大经典排序算法详解
  • Linux--软硬链接、动静态库
  • 内核ICMP协议分析
  • 使用excel.EasyExcel实现导出有自定义样式模板的excel数据文件,粘贴即用!!!
  • C# 项目06-计算程序运行时间
  • mysql 对json的处理?
  • deepseek使用记录25——当反思失效了
  • AI工具如何改变编程学习?Trae IDE与Claude 3.5的实践案例
  • 使用AI一步一步实现若依(18)
  • SpringBoot整合MQTT最详细版(亲测有效)
  • 基于springboot的教师工作量管理系统(031)
  • 同旺科技USB to I2C 适配器 ---- 指令循环发送功能
  • Linux系统——keepalived安装与部署
  • Eplan许可分析
  • 嵌入式芯片与系统设计竞赛,值得参加吗?如何选题?需要学什么?怎么准备?
  • 智能照明与新能源集成的精细化能效管理实践
  • 2020年全国职业院校技能大赛改革试点赛高职组“云计算”竞赛赛卷
  • 性能优化中如何“避免链接关键请求”