中断控制与实现

一、中断基本概念

1、中断

        中断是一种异步事件,用于通知处理器某个事件已经发生,需要处理器立即处理。由于I/O操作的不确定因素以及处理器和I/O设备之间的速度不匹配I/O设备可以通过某种硬件信号异步唤醒对应的处理器的响应,这些硬件信号叫做中断Linux系统中断使用的是中断编号,这个中断编号是由引脚编号获取的,引脚编号就是芯片对应的GPIO口。

2、中断线

        在允许中断的计算机系统中,通常会有一个中断控制器---相当于NVIC,可以控制多路独立的中断,挂载多条中断线,有些中断线是专用的,而有些中断线可以被多个外设共享(共享中断)。Linux系统中断,灵活性更高---因为中断编号和中断服务函数是利用API函数进行绑定的。中断服务函数是自定义的,还可以带参数(好处就在于可以利用参数判断,当前是哪个引脚动作了)

3、IRQ--interrupt request

        Linux内核为每个中断线分配一个唯一的中断编号(IRQ,也称为中断请求号。不同的计算机系统分配方式不同的。

4、ISR--interrupt service routine

        系统响应外设的中断处理是过外设所在的中断线上绑定的中断服务例程/中断服务函数ISR)实现的

注意:一个中断线可以绑定一个或多个中断服务例程,多个中断线可以绑定同一个中断服务例程。

总结:

        1:多对一、一对多    

        2:一个外设只能对应一个中断服务例程。

5、中断工作机制

中断处理过程:

        当外设发出中断请求时,会通过所在的中断线传递给系统,处理器会停止当前的工作,保存断点,然后跳到该中断线所绑定的中断服务函数(ISR),执行相应的中断服务函数。中断服务处理完毕后,处理器再返回到断点继续执行原来的工作。

6、共享中断和重用中断服务函数的区别

(1)挂载不同中断线上的不同外设,通常使用不同的中断服务例程,无需通过设备参数进行区分。如果这些外设中断服务处理的框架是相同,则可以使用同一个中断服务例程,通过设备参数来区分不同的外设。这个情况不是共享中断,而是重用中断服务例程。

(2)挂载同一个中断线上的不同外设,使用同一个中断服务例程,必须参数来区分不同的外设此时称为共享中断

7、中断顶半部和中断底半部

中断不能占用处理器太久的时间,否则会影响系统的性能,但是有些中断事务比较耗时,怎么办?

        Linux内核将中断分为顶半部和低半部机制中断服务例程属于顶半部,在中断服务例程结束之前可以启动中断底半部机制,将耗时的事务交给中断低半部处理。

        顶半部通常会立即执行,时效性更高。而底半部是尽快执行,不能保证立即执行,而是内核择机调度,有机会就执行。常用的底半部机制包括软中断softirq、tasklet和工作队列。softirq和tasklet运行于中断上下文,不能睡眠工作队列workqueue运行于进程上下文,可以睡眠。核定时器timer作为定时机制,可以用于中断底半部,实现精准的延时功能。

总结:

1:linux系统中断控制不允许中断嵌套
2:中断控制分为中断顶半部和中断底半部

3:运行于中断上下文​​(不能睡眠):中断顶半部​​、​​定时器回调​​​​、软中断SoftIRQ、tasklet​​
     ​​运行于进程上下文(能睡眠)​​: ​​Workqueue

二、中断顶半部相关函数

1、中断的注册和注销

request_irq

函数功能:

        注册中断,绑定对应的中断服务函数,并指明中断的中断编号,中断属性,设备参数

头文件:

        #include <linux/interrupt.h>

函数原型:

        int request_irq(unsignedint irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev);

函数参数:

        unsignedint irq:指定的中断编号

        irq_handler_t handler:中断服务函数。这是一个指向函数的指针,当指定的中断发生时,该函数将被调用。中断处理函数必须符合特定的原型,通常接受两个参数:表示中断号的中断号和表示设备标识的 void * 指针。--->原型:typedef irqreturn_t (*irq_handler_t)(int, void *);返回值为IRQ_HANDLED;

        unsigned long flags:中断属性,指的是触发条件-->上升沿或则下降沿

        const char *name:中断的名称。这是一个字符串,用于标识请求中断的设备。作用:内核会根据这个名字创建/proc/irq/中断编号/name/ 目录spurious这个文件会记录中断发生的次数

        void *dev:这个参数是传递给中断服务函数。对共享中断来说,这个参数一定有要,当注销共享中断中的其中一个时,用这个指定来标识要注销哪一个。对于有惟一入口的中断,可以传递NULL,但是一般来说都会传递一个有意义指针,在中断程序中使用,以方便编程

函数返回值:

        成功 返回  0

        失败 返回 <0(指定的中断号已经被另一个设备占用,或者没有足够的资源来分配中断处理程序)

Irq如何获取?

如果是GPIO外设中断,可以查询GPIO引脚编号即可因为中断编号是由GPIO引脚编号获取的,引脚编号就是芯片对应的GPIO口。

gpio_to_irq

函数功能:

        获取指定的GPIO引脚编号的中断编号

头文件

        #include <linux/gpio.h>

函数原型

        int gpio_to_irq(unsigned gpio)

函数参数:

        unsigned gpio:GPIO口引脚编号  

函数返回值

        成功 >=0  表示对应引脚的中断编号  失败 <0

free_irq

函数功能:

        注销指定中断线的指定的设备,取消该设备触发中断

头文件

        #include <linux/interrupt.h>

函数原型

        void free_irq(unsigned int irq,void *dev_id);

函数参数:

        unsigned int irq:代表指定中断线上的中断编号  

        void *dev_id:挂在该中断线上的设备的标识信息,必须与request_irq保持一致

函数返回值

2、中断的使能和失能

enable_irq

函数功能:

        使能处理器上的中断线

头文件

        #include <linux/interrupt.h>

函数原型

        void  enable_irq(unsigned int irq)

函数参数:

        unsigned int irq:代表指定中断线上的中断编号  

函数返回值

disable_irq

函数功能:

        禁止指定的中断线

头文件

        #include <linux/interrupt.h>

函数原型

        void disable_irq(unsigned int irq)

函数参数:

        unsigned int irq:代表指定中断线上的中断编号  

函数返回值

disable_irq_nosync

函数功能:

        禁用一次中断线,不会等待该中断线上的中断服务例程,可以用于中断上下文

头文件

        #include <linux/interrupt.h>

函数原型

        void disable_irq_nosync(unsigned int irq)

函数参数:

        unsigned int irq:代表指定中断线上的中断编号  

函数返回值

 3、示例

内核源码里面有对应的中断触发方式:

#define  IRQF_TRIGGER_RISING   0x00000001//上升沿

#define  IRQF_TRIGGER_FALLING  0x00000002//下降沿

利用按键(原理图),转化引脚编号,定义一个结构体对象,触发中断动作

代码编写思路:

应用层->app-打开对应的文件设备节点,read读取按键的状态(低电平-按键按下),紧接着判断获取到的电平状态并且打印显示按键按下或者按下抬起

驱动层->杂项字符设备驱动模型+硬件操作--按键(GPIO0_A5-中断注册)--中断服务函数(获取按键引脚的电平状态)---xxx_read中将数据上传到应用层cope_to_user()

//应用层编程步骤

按下按键,将按键的状态显打印出来

//驱动程序编程步骤

第一步:入口函数的实现

  1. 检查给定的 GPIO编号是否有效
  2. 注册指定的GPIO引脚资源
  3. 将指定的GPIO引脚设置为输模式
  4. 获取指定的GPIO的中断编号
  5. 注册中断,绑定对应的中断服务函数
  6. 注册杂项设备驱动模型

第二步:中断服务函数的具体实现

  1. 获取按键的电平状态-gpio_get_value()
  2. 将获取到的电平值,保存到k_buf里面

第三步:read函数的具体实现

        1.将获取到的数据上传到应用层->cope_to_user()

第四步:出口函数的实现

        1.释放指定的GPIO

        2.释放指定中断线

        3.注销杂项设备驱动模型

//定义一个key引脚相关的结构体

struct key_init{

    int num;        //序号

    int gpio;       //引脚编号

    char name[10];  //引脚名称

    int irq;        //中断号

    int flag; //触发方式

};

//声明变量

static struct key_init keys[]={

       {0,5,"key0",0,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING},

       {},

};

应用层代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//调用一下open函数fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//获取驱动层硬件的电平状态read(fd,r_buf,1);//避免重复显示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//标志,类似于第一次开机//判断获取到的电平状态  0-按键按下  1-按键抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//关闭对应的文件描述符close(fd);   
}

 驱动层代码:

//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include  <linux/interrupt.h>char k_buf[2]={0};
//定义一个key引脚相关的结构体
struct key_init{int num;        //序号int gpio;       //引脚编号char name[10];  //引脚名称int irq;        //中断号unsigned long flag;			//触发方式
};//声明变量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING},{},
};//中断服务函数
static irqreturn_t xxx_key_handler(int irq, void *dev)
{int val;//获取按键的电平状态-gpio_get_value()val = gpio_get_value(keys[0].gpio);//按键按下-0 按键抬起-1printk("val:%d\r\n",val);//将获取到的电平值,保存到k_buf里面k_buf[0] = val + '0';//转为字符return IRQ_HANDLED;
}//定义一个xxx_read---read-读操作实现
//应用层的读函数,对应驱动中的读函数//1-在驱动层获取对应引脚状态//2-将对应的引脚状态给用户层
static ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{	int res;//将获取到的数据上传到应用层->cope_to_userres = copy_to_user(buf,k_buf,1);if(res == 0){printk("cope_to_user success\r\n");}return 0;
}//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("Goodbye\r\n");printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);return 0;
}// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{.read=xxx_read,.release=xxx_release,
};//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{.minor=255, // 次设备号.name="key_misc",// 设备名称.fops=&pfop,//指向文件操作方法集合的指针};//入口函数
static int __init hello_init(void)
{int ret;//1-检查给定的 GPIO编号是否有效ret = gpio_is_valid(keys[0].gpio);if(ret == 1){printk("gpio_is_valid success\r\n");}//2-注册指定的GPIO引脚资源ret = gpio_request(keys[0].gpio,keys[0].name);if(ret == 0){printk("gpio_request success\r\n");}//3-将指定的GPIO引脚设置为输入模式ret = gpio_direction_input(keys[0].gpio);if(ret == 0){printk("gpio_direction_input set success\r\n");}//4-获取指定的GPIO的中断编号keys[0].irq = gpio_to_irq(keys[0].gpio);printk("irq:%d\r\n",keys[0].irq);//5-注册中断,绑定对应的中断服务函数ret=request_irq(keys[0].irq, xxx_key_handler, keys[0].flag,keys[0].name,NULL);if(ret == 0){printk("request_irq success\r\n");}//6-注册杂项设备驱动模型ret=misc_register(&pdevc);if(ret == 0){printk("misc_register success\r\n");}return 0;
}//出口函数
static void __exit hello_exit(void)
{//1-释放指定的GPIOgpio_free(keys[0].gpio);//2-释放指定中断线free_irq(keys[0].irq,NULL);//3-注销杂项设备misc_deregister(&pdevc);printk("misc_deregister success\r\n");}//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

现象:

三、内核定时器---timer 

1、系统定时器

        系统定时器的作用:定时时间一到,进入到对应的定时处理函数里面。

        系统定时器有硬件定时器,可以设置的频率,频率--(周期):1秒内系统定时触发中断的次数,HZ表示,也就是1s内最多可以触发1000次中断

如果频率越高,定时时间间隔越小,导致开销和电源的消耗越高。频率高之后,会非常耗电。

注意:系统定时器的计时单位jiffy(系统时间单位)

Linux内核源码里面专门提供了对应的时间转换函数:

内核标准计数单位与系统定时器的计时单位的换算有专门的函数

msecs_to_jiffies

功能:

        将以毫秒为单位的时长转换为以jiffy为单位的时长—>给内核定时器使用的

头文件

        #include <linux/jiffies.h>

原型:

        unsigned long msecs_to_jiffies(const unsigned int m)

参数

        m:毫秒数

返回值:

        相同时长的jiffy的个数

usecs_to_jiffies

功能:

        将微秒为单位的时长转换为以jiffy为单位的时长

头文件

        #include <linux/jiffies.h>

原型:

        unsigned long usecs_to_jiffies(const unsigned int m)

参数

        m:微秒数

返回值:

        相同时长的jiffy的个数

jiffies

        在 Linux 内核中,jiffies 是一个全局变量,用于跟踪自系统启动以来经过的时钟滴答数(clock ticks)。每个时钟滴答代表了一定的时间长度,这个时间长度由内核配置中的 HZ 值决定。HZ 定义了每秒的时钟滴答数,因此每个滴答的时间长度是 1/HZ 秒。

例如:

定时 10ms --->jiffies +msecs_to_jiffies(10)

定时 10us --->jiffies +usecs_to_jiffies(10)

为什么需要加上jiffies?

内核使用 jiffies 来跟踪时间,并且所有的定时器和延迟操作都是基于 jiffies 的。因此,为了与内核的时间管理系统保持一致,你需要使用 jiffies 作为起点。

2、内核定时器

Linux 内核定时器采用系统时钟来实现,只需要提供超时时间(定时值)和定时处理函数即可。

内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

核心数据结构->timer_list

内核源码里面的基本形式:

struct timer_list
{struct list_head entry;unsigned long expires; /* 定时器超时时间/到期时间, 单位是节拍数 */struct tvec_base *base;void (*function)(unsigned long); /* 定时处理函数指针 */unsigned long data; /* 要传递给 function 函数的参数 */int slack;
};

3、相关函数

DEFINE_TIMER

功能

        静态初始化定时器,会把核心数据结构进行初始化

头文件

        #include <linux/time.h>

宏原型

        #define DEFINE_TIMER(_name,_function,_expires,_data);

参数

        _name:定义struct timer_list结构体变量的名称 (不用取地址)

        _function:绑定超时处理函数的名字 --->原型:void (*function)(unsigned long);

        _expires:指的是设置到期时间  注:初始化一个静态结构体变量时,需要使用常量表达式作为初始化值

        _data:表示出传递给任务函数的参数

返回值: 无

注意:需要定义在全局,不能在入口函数内定义

setup_timer

功能:

        动态初始化定时器,会把核心数据结构(除到期时间外)进行初始化

原型:

        #define setup_timer(timer, fn, data)

头文件

        #include <linux/time.h>

参数:

        timer:定时器结构变量地址。这通常是一个指向 struct timer_list 类型的指针

        fn:超时处理函数--->原型:void (*function)(unsigned long);

        data :传递给超时处理函数的参数

返回值:无

注意:这个宏并没有初始化 expires成员,所在启动定时前必须初始化这个成员。

add_timer

功能:

        注册指定的timer并启动,一旦到达定时时间就会触发定时中断并执行超时处理函数

头文件

        #include <linux/time.h>

宏原型:

        void add_timer(struct timer_list *timer) 

参数

        struct timer_list *timer:指向要删除的 timer_list 结构的指针

返回值:无

del_timer_sync

函数功能:

        从内核定时器队列中同步删除一个定时器

头文件:

        #include <linux/time.h>

函数原型:

        int del_timer_sync(struct timer_list *timer);

参数

        struct timer_list *timer:指向要删除的 timer_list 结构的指针

返回值:

        成功 返回0

        失败 返回<0

mod_timer

函数功能:

        用于修改已经存在于内核定时器队列中的定时器的到期时间。这意味着如果定时器当前已经在队列中,mod_timer会更新它的expires字段,以反映新的到期时间,而无需从队列中移除并重新添加它。

函数原型:

        int mod_timer(struct timer_list *timer, unsigned long expires);

参数:

        struct timer_list *timer:指向要修改的timer_list结构的指针。

        unsigned long expires:定时器的新到期时间,通常以jiffies加上一个延迟值来表示。

返回值:

        成功 返回1

        失败(如果定时器不在队列中,也就是之前从未被添加或已经被删除),则返回0

注意:mod_timer只能修改已经在队列中的定时器。如果定时器尚未被添加到队列中,调用mod_timer将导致未定义行为(通常是内核错误或崩溃)

4、示例

1:动态初始化定时器并且触发定时中断,从而执行超时处理函数

代码编写思路:

第一步:入口函数

  1. 动态初始化定时器
  2. 设置到期时间
  3. 注册指定的timer并启动

第二步:超时处理函数

打印初始化传过来的参数

第三步:出口函数

注销定时器

代码:

//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>//定义结构体变量
struct timer_list list_timer;//超时处理函数
static void xxx_function(unsigned long data)
{printk("data:%ld\r\n",data);}//入口函数
static int __init hello_init(void)
{//1-动态初始化定时器—动态定义setup_timer(&list_timer,xxx_function,666);//2-设置到期时间list_timer.expires = jiffies + msecs_to_jiffies(5000);//3-注册指定的timer并启动add_timer(&list_timer);printk("add success\r\n");return 0;
}//出口函数
static void __exit hello_exit(void)
{int ret;//注销定时器ret = del_timer_sync(&list_timer);if(ret == 0){printk("del success\r\n");}}//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

现象:

修改上面程序,将字符串传到超时处理函数

代码:

//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>//定义结构体变量
struct timer_list list_timer;//超时处理函数
static void xxx_function(unsigned long data)
{printk("data:%s\r\n",(char *)data);//%s格式说明符告诉printk函数沿着这个指针一直打印字符,直到遇到null字符(\0)为止}//入口函数
static int __init hello_init(void)
{//1-动态初始化定时器setup_timer(&list_timer,xxx_function,(unsigned long*)"hello_world");//2-设置到期时间list_timer.expires = jiffies + msecs_to_jiffies(5000);//3-注册指定的timer并启动add_timer(&list_timer);printk("add success\r\n");return 0;
}//出口函数
static void __exit hello_exit(void)
{int ret;//注销定时器ret = del_timer_sync(&list_timer);if(ret == 0){printk("del success\r\n");}}//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

现象:

2:静态初始化定时器并且触发定时中断,从而执行超时处理函数

代码编写思路:

定义全局->静态初始化定时器

第一步:入口函数

        注册指定的timer并启动

第二步:超时处理函数

        打印初始化传过来的参数

第三步:出口函数

        注销定时器

代码:

//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>
//定义结构体变量
struct timer_list list_timer;//超时处理函数
static void xxx_function(unsigned long data)
{printk("data:%ld\r\n",data);}//静态初始化定时器
DEFINE_TIMER(list_timer,xxx_function,HZ*5,666);//入口函数
static int __init hello_init(void)
{//定义对应的时间list_timer.expires = msecs_to_jiffies(5000);//3-注册指定的timer并启动add_timer(&list_timer);printk("add success\r\n");return 0;
}//出口函数
static void __exit hello_exit(void)
{int ret;//注销定时器ret = del_timer_sync(&list_timer);if(ret == 0){printk("del success\r\n");}}//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

四、中断底半部---tasklet

        在Linux 内核中,tasklet 是一种轻量级的、基于软中断的线程化机制,用于执行延迟的任务或工作。tasklet在中断上下文中执行,这意味着它们不能睡眠。因此,tasklet中的任务必须是简短且高效的,不能包含可能导致睡眠的函数调用。

1、核心数据结构-> tasklet_struct

内核源码里面的基本形式:

struct tasklet_struct{struct tasklet_struct *next;   //实现链表管理,内核自动实现unsigned long state;           //标识tasklet状态,内核自动实现atomic_t  count;               //标识tasklet是否被激活 0激活  非0没有激活void (*func)(unsigned long data);  //任务函数指针,需要指向自定义的任务函数unsigned long data;};

2、相关函数和

DECLARE_TASKLET

功能

        静态定义并初始化一个tasklet对象,处于激活状态

头文件

        #include <linux/interrupt.h>

宏原型

        #define DECLARE_TASKLET(name,func,data)

参数

        name:自定义的struct tasklet_struct对象的名称(不用取地址)

        func:tasklet对应的函数地址

        data:传递给函数的参数

返回值

tasklet_init

函数功能

        动态初始化一个已经定义好的struct tasklet_struct对象,使得处于激活状态

头文件

        #include <linux/interrupt.h>

函数原型

        void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data)

函数参数

        struct tasklet_struct*t:指向struct tasklet_struct结构的指针

        functasklet对应的函数地址

        unsigned long data传递给函数的参数

函数返回值

tasklet_disable

函数功能

        指定tasklet的count成员减1 ,试图使得tasklet处于非激活的状态

头文件

        #include <linux/interrupt.h>

函数原型

        void tasklet_disable(struct tasklet_struct *t)

函数参数

        struct tasklet_struct*t:指向struct tasklet_struct结构的指针

函数返回值

tasklet_schedule

函数功能

        把指定的 tasklet 添加到当前 CPU 的 tasklet 队列中。在内核的适当时间(通常是当前正在执行的中断处理程序或软中断完成后),tasklet 的执行函数会被调用

头文件

        #include <linux/interrupt.h>

函数原型

        void tasklet_schedule(struct tasklet_struct *t)

函数参数

        struct tasklet_struct*t:指向struct tasklet_struct结构的指针

函数返回值

注意:tasklet处于激活状态,调用便执行绑定函数。

tasklet_kill

函数功能

        取消调度指定的tasklet,将该tasklet从tasklet调度链表上进行移除。注意:如果tasklet绑定函数正在执行,则会等待该函数执行完成

头文件

        #include <linux/interrupt.h>

函数原型

        void tasklet_kill(struct tasklet_struct *t)

函数参数

        struct tasklet_struct*t:指向struct tasklet_struct结构的指针

函数返回值

3、示例

1:动态初始化tasklet_struct,从而执行tasklet处理函数

代码编写思路:

第一步:入口函数

  1. 动态初始化tasklet
  2. 调度对应的tasklet

第二步:tasklet处理函数

打印初始化传过来的参数

第三步:出口函数

注销tasklet

代码:

//第一个实例代码:利用tasklet基础模块
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>//定义对应的核心结构体
static struct tasklet_struct list_tasklet;//定义一下中断底半部的处理函数
static void tasklet_func(unsigned long data)
{printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); //再输出一下对应的dataprintk("data=%s\r\n",(char *)data);}static int __init xxx_init(void)
{  //初始化核心结构体对象tasklet_init(&list_tasklet,tasklet_func,(unsigned long)"world");//调度一下tasklet_schedule(&list_tasklet);printk("tasklet_init success\r\n");return 0;
} static void __exit xxx_exit(void)
{//注销对应的tasklettasklet_kill(&list_tasklet);printk("tasklet_kill success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

现象:

[   70.752606] tasklet_init success

[   70.752662] name:tasklet_func,line:14

[   70.752719] data=world

2:静态初始化tasklet_struct,从而执行tasklet处理函数

代码编写思路:

定义全局->静态初始化tasklet

第一步:入口函数

调度对应的tasklet

第二步:tasklet处理函数

打印初始化传过来的参数

第三步:出口函数

注销tasklet

代码:

#include <linux/module.h>
#include <linux/init.h>#include <linux/interrupt.h>//定义对应的核心结构体
static struct tasklet_struct list_tasklet;//定义一下中断底半部的处理函数
static void tasklet_func(unsigned long data)
{printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); //再输出一下对应的dataprintk("data=%s\r\n",(char *)data);}//采用对应的静态定义的方式实现
DECLARE_TASKLET(list_tasklet,tasklet_func,(unsigned long)"123456");static int __init xxx_init(void)
{     //调度一下tasklet_schedule(&list_tasklet);printk("tasklet_init success\r\n");return 0;
} static void __exit xxx_exit(void)
{//注销对应的tasklettasklet_kill(&list_tasklet);printk("tasklet_kill success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

现象:

[   70.752606] tasklet_init success

[   70.752662] name:tasklet_func,line:14

[   70.752719] data=123456

动态初始化与静态初始化的区别?

动态初始化需要在运行时动态创建,而静态初始化需要在编译时定义。

注意事项

(1)如果某个非激活的tasklet被调度了,但是在模块卸载的时候没有删除,则内核崩溃。

(2)如果某个非激活的tasklet被调度了,对它做删除操作,会引起阻塞。

3)如果希望在中断上下文中实现延时操作,应该使用mdelay()函数-->可以一直占用CPU如果使用msleep()进行休眠会导致内核崩溃。

 五、中断底半部---工作队列

        工作队列中的任务在内核线程中执行,这意味着它们运行在进程上下文中。因此,工作队列中的任务可以执行那些可能导致睡眠的函数,如文件I/O操作或内存分配等。由于工作队列可以睡眠,因此它们非常适合执行那些需要等待资源或进行复杂操作的任务。

1、工作队列的组成

工作队列主要由以下几个部分组成:

工作项(work_struct):工作项是一个结构体,它通常通过INIT_WORK宏进行初始化,并指定一个回调函数(work function)。当工作项被执行时,会调用这个回调函数。通常在共享工作队列的情况下,不需要创建工作队列(workqueue_struct)只需要创建工作项(work_struct)

工作队列(workqueue_struct):工作队列是一个结构体,它包含了多个工作项的链表以及用于保护工作队列状态的互斥锁等。工作队列可以由多个工作者线程并发访问和执行其中的工作项。通常在私人工作队列的情况下,需要创建工作队列(workqueue_struct)也需要需要创建工作项(work_struct)

工作者线程(worker thread):工作者线程是专门用于执行工作队列中工作项的内核线程。它会循环地从工作队列中取出工作项并执行其回调函数。

2、核心数据结构->workqueue_structstruct work_struct

struct workqueue_struct

struct workqueue_struct 负责管理和调度工作项(work_struct),确保这些工作项能够在适当的时机被内核线程执行。

内核源码的基本形式:

struct workqueue_struct {struct list_head pwqs;		/* WR: all pwqs of this wq */struct list_head list;		/* PR: list of all workqueues */struct mutex	mutex;		/* protects this wq */int	work_color;	/* WQ: current work color */int	flush_color;	/* WQ: current flush color */
}

 工作队列的创建通常使用 create_workqueue 函数,销毁则使用 destroy_workqueue 函数。

struct work_struct

struct work_struct 表示工作队列中的一个工作项或任务。它包含了执行任务所需的所有信息,并需要被添加到工作队列 struct workqueue_struct 中才会被调度和执行。

内核源码的基本形式:

struct work_struct {atomic_long_t data;//用于存储与工作项相关的数据,内核自动实现struct list_head entry;//用于将工作项链接到工作队列的待处理列表中,内核自动实现work_func_t func;//func 字段指向一个处理函数,当工作项被执行时,该函数将被调用#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};

工作项的初始化通常使用 INIT_WORK 宏,随后使用 queue_work、queue_work_on、queue_delayed_work 或 queue_delayed_work_on 函数将其添加到工作队列中

总结:

struct workqueue_struct 是工作队列的管理和调度结构体,负责管理和调度工作项。

struct work_struct 表示工作队列中的一个工作项或任务,包含了执行任务所需的所有信息。

3、共享工作队列

特点:

        Linux内核会自动为每个处理器创建一个全局的共享工作队列,驱动模块只需将自己的工作项(work)挂载到当前处理器的共享工作队列中。

优点:简单、快捷,方便,不会占用系统的资源。

缺点:

如果挂载的工作项过多,可能会导致处理延迟,影响任务的时效性。

由于是共享资源,不同驱动模块的工作项可能会相互影响。

使用流程:

动态或者静态初始化work_struct

调度对应的work,执行相应的处理函数

注销work

4、私有工作队列

特点:

        私有工作队列是驱动模块自建的,仅供该模块的工作项使用,可以根据任务需求自定义工作队列的优先级和处理函数。

优点:不受其他模块工作项的影响,保证了任务的独立性和时效性。

缺点:需要占用额外的系统资源,需要手动创建和管理工作队列和工作项。

使用流程:

动态定义并初始化一个工作队列对象(workqueue_struct)

动态或者静态初始化(work_struct) 

将一个工作项(work_struct)添加到指定的工作队列(workqueue_struct)

调度对应的work,执行相应的处理函数

注销work、workqueue

总结:

①共享工作队列适用于对时效性要求不高、任务量适中的场景,其实现简单且资源利用率高。

②私有工作队列适用于对时效性要求高、需要独立执行任务的场景,虽然实现复杂且占用资源较多,但能够提供更好的任务隔离和性能保障。

 

5、相关函数

create_workqueue

功能:

        为每个处理器静态定义并初始化一个工作队列对象(workqueue_struct)

头文件:

        #include <linux/workqueue.h>

宏原型:

        #define create_workqueue(name)  alloc_workqueue(name, WQ_MEM_RECLAIM, 1)

参数:

        name:自定义的工作队列的名称,是一个字符串

返回值:

        alloc_workqueue函数会创建一个新的工作队列,并返回一个指向该工作队列的struct workqueue_struct指针        

destroy_workqueue

函数功能:

        销毁指定的私有工作队列(workqueue_struct)

头文件:

        #include <linux/workqueue.h>

函数原型:

        void destroy_workqueue(struct workqueue_struct *wq);

函数参数:

        struct workqueue_struct *wq:指向目标工作队列的指针

返回值:无

DECLARE_WORK

功能:

        用于静态初始化一个工作项 work_struct 结构体

头文件:

        #include <linux/workqueue.h>

宏原型:

        #define DECLARE_WORK(name, func)

参数:

        name:想要声明的 work_struct 变量的名称

        func:当工作项被执行时应该调用的处理函数

返回值:无

INIT_WORK

功能:

        用于动态初始化一个工作项 work_struct 结构体

头文件:

        #include <linux/workqueue.h>

宏原型:

        #define INIT_WORK(_work, _func)

参数:

        _work:一个指向 work_struct 结构体的指针,表示你想要初始化的工作项。在调用 INIT_WORK 宏之前,通常已经声明了这个 work_struct 变量  

        _func:一个函数指针,指向当你将工作项添加到工作队列并且工作队列线程变得可用时要执行的处理函数 函数原型:typedef void (*work_func_t)(struct work_struct *work);

返回值:无

queue_work

函数功能:

        用于将一个工作项(work_struct)添加到指定的工作队列(workqueue_struct)中以供稍后执行

头文件:

        #include <linux/workqueue.h>

函数原型:

        static inline bool queue_work(struct workqueue_struct *wq, struct work_struct *work);

函数参数:

        struct workqueue_struct *wq指向目标工作队列的指针

        struct work_struct *work:指向要添加的工作项的指针。这个工作项在使用前应该通过 INIT_WORK 或 INIT_DELAYED_WORK 宏进行初始化,并指定一个处理函数

返回值:

        如果工作项成功添加到工作队列中,函数返回 true。

        如果由于某种原因(例如工作队列正在被销毁)工作项无法被添加,函数返回 false。

schedule_work

函数功能:

        调度指定的work对象,将该work对象添加到处理器的共享队列中

头文件:

        #include <linux/workqueue.h>

函数原型:

        bool schedule_work(struct work_struct *work)

函数参数:

        struct work_struct *work:指向要添加的工作项的指针。这个工作项在使用前应该通过                                                  INIT_WORK 或 INIT_DELAYED_WORK 宏进行初始化,

                                                并指定一个处理函数

返回值:

        成功:1    失败:0

bool cancel_work_sync

函数功能:

        取消指定的普通work对象,将该work对象从当前处理器的共享队列中移除

头文件:

        #include <linux/workqueue.h>

函数原型:

        bool cancel_work_sync(struct work_struct *work)

函数参数:

        struct work_struct *work:指向要添加的工作项的指针。这个工作项在使用前应该通过 INIT_WORK 或 INIT_DELAYED_WORK 宏进行初始化,并指定一个处理函数

返回值:

        成功:1    失败:0

6、示例

共享工作队列的实现

1:动态初始化work_struct,从而执行work处理函数

代码编写思路:

定义work核心结构体对象

第一步:入口函数

  1. 动态初始化work
  2. 调度对应的work

第二步:work处理函数

第三步:出口函数

        注销work

代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定义work核心结构体对象
static struct work_struct work_lis;//work处理函数
void xxx_workfunc(struct work_struct *work)
{//输出一下对应的行号printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//模块入口函数
static int __init xxx_init(void)
{//初始化--动态INIT_WORK(&work_lis,xxx_workfunc);//调度schedule_work(&work_lis);printk("schedule_work success\r\n");return 0;
}//模块的出口函数
static void __exit xxx_exit(void)
{//取消对应工作对象cancel_work_sync(&work_lis);printk("cancel_work_sync success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

 

2:静态初始化work_struct,从而执行work处理函数

代码编写思路:

定义work核心结构体对象

定义全局->静态初始化work

第一步:入口函数

        调度对应的work

第二步:work处理函数

第三步:出口函数

        注销work

代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定义work核心结构体对象
static struct work_struct work_lis;//work处理函数
void xxx_workfunc(struct work_struct *work)
{//输出一下对应的行号printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//静态定义
DECLARE_WORK(work_lis,xxx_workfunc);//模块入口函数
static int __init xxx_init(void)
{//初始化一下--动态//INIT_WORK(&work_lis,xxx_workfunc);//调度一下schedule_work(&work_lis);printk("schedule_work success\r\n");return 0;
}//模块的出口函数
static void __exit xxx_exit(void)
{//取消对应工作对象cancel_work_sync(&work_lis);printk("cancel_work_sync success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

 

私有工作队列的实现

利用一下私有工作队列,观察一下对应的现象

代码编写思路:

定义workqueue核心结构体对象

定义work核心结构体对象

第一步:入口函数

  1. 创建对应的工作队列workqueue
  2. 动态初始化work
  3. 将工作项(work_struct)添加到指定的工作队列(workqueue_struct)
  4. 调度对应的work,执行相应的处理函数

第二步:work处理函数

第三步:出口函数

        注销work、workqueue

代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定义workqueue——stuct核心结构体对象
static struct workqueue_struct *wq;
//定义work_stuct核心结构体对象
static struct work_struct work_lis;//work处理函数
void xxx_workfunc(struct work_struct *work)
{//输出一下对应的行号printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//模块入口函数
static int __init xxx_init(void)
{bool ret;//创建对应的工作队列wq=create_workqueue("wque");//初始化一下--动态---将工作队列上,绑定对应的处理函数INIT_WORK(&work_lis,xxx_workfunc);//将对应的工作对象,添加到对应的工作队列上ret=queue_work(wq,&work_lis);
if(ret == 1)
{printk("add success\r\n");
}//调度schedule_work(&work_lis);printk("schedule_work success\r\n");return 0;
}//模块的出口函数
static void __exit xxx_exit(void)
{//取消对应工作对象cancel_work_sync(&work_lis);printk("cancel_work_sync success\r\n");//销毁对应的工作队列destroy_workqueue(wq);printk(" destroy_workqueue\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");


总结​​:

​​共享工作队列​​:
        ​​利​​:零配置、零开销
        ​​弊​​:无隔离、延迟不可控
        ➠ 仅用于​​非关键低频任务​​
​​私有工作队列​​:
​​        利​​:低延迟、强隔离、优先级可控
        ​​弊​​:需手动管理生命周期
        ➠ ​​实时系统/硬件交互/关键服务​​的基石
⚙️ ​​终极决策树​​:

        任务是否需要硬实时保障? → ​​私有队列 + WQ_HIGHPRI​​
        任务是否与硬件 I/O 相关? → ​​私有队列绑核​​
        任务运行频率是否高于 1000次/秒? → ​​私有队列调优 max_active​​
        否则 → ​​共享队列省资源​

7、指定延时工作队列

        指定延时工作队列是一种特殊类型的工作队列,它允许将任务安排在指定的延迟时间后执行。任务在添加到队列后,不会立即执行,而是等待指定的延迟时间到达后才执行。

①核心数据结构->delayed_work

        delayed_work 允许你将一个工作项排队到系统的共享工作队列中,并指定一个延迟时间,之后该工作项将被调度执行。

内核源码的基本形式:

struct delayed_work{struct work_struct work;   //代表延时workstruct timer_list timer;      //内核定时器的核心结构
};
②相关函数和宏
DECLARE_DELAYED_WORK

内核源码的基本形式:

#define DECLARE_DELAYED_WORK(n, f)					\struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)

功能:

        用于静态初始化一个 delayed_work 结构体

头文件:

        #include <linux/workqueue.h>

宏原型:

        #define DECLARE_DELAYED_WORK(n,f)

参数:

        n:自定义的delay_work对象的名称   

        f:已经定义并实现好的delayed_work对象所绑定的任务函数

返回值:无

INIT_DELAYED_WORK

内核源码的基本形式:

#define INIT_DELAYED_WORK(_work, _func)					\__INIT_DELAYED_WORK(_work, _func, 0)

功能:

        用于动态初始化一个delayed_work结构体

头文件:

        #include <linux/workqueue.h>

宏原型:

        #define INIT_WORK(_work, _func)

参数:

        _work:一个指向 delay_work结构体的指针,表示你想要初始化的工作项。在调用 INIT_WORK 宏之前,通常已经声明了这个 delay_work 变量  

        _func:已经定义并实现好的delayed_work对象所绑定的任务函数

返回值:无

 

schedule_delayed_work

函数功能:

        调度指定的delayed_work对象,将该delay_work对象添加到对应的共享工作队列中

头文件:

        #include <linux/workqueue.h>

函数原型:

        bool schedule_delayed_work(struct delayed_work *work,unsigned long delay)

函数参数:

        struct work_struct *work:指定的delayed_work对象的地址

        unsigned long delay:延时的时长

返回值:

        成功:1    失败:0

bool cancel_work_sync

函数功能:

        取消指定的delayed_work对象,将该delayed_work对象从私有工作队列中移除

头文件:

        #include <linux/workqueue.h>

函数原型:

        bool cancel_delayed_work_sync(struct delayed_work *dwork);

函数参数:

        struct delayed_work *dwork:指定的delayed_work对象的地址

返回值:

        成功:1    失败:0

③示例

1:动态定义并初始化delay_work  

代码编写思路:

定义delay_work核心结构体对象

第一步:入口函数

  1. 动态初始化delay_work
  2. 调度对应的delay_work 

第二步:delay_work处理函数

第三步:出口函数

        注销delay_work

代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定义delay_work核心结构体对象
static struct delayed_work work_lis;//delay_work处理函数
void xxx_workfunc(struct work_struct *work)
{//输出一下对应的行号printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//模块入口函数
static int __init xxx_init(void)
{//初始化一下--动态延时的workINIT_DELAYED_WORK(&work_lis,xxx_workfunc);//调度schedule_delayed_work(&work_lis,msecs_to_jiffies(3000));printk("schedule_delayed_work success\r\n");return 0;
}//模块的出口函数
static void __exit xxx_exit(void)
{//取消对应工作对象cancel_delayed_work_sync(&work_lis);printk("cancel_delayed_work_sync success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

2:静态定义并初始化delay_wrok

代码编写思路:

定义delay_work核心结构体对象

定义全局->静态初始化delay_work

第一步:入口函数

调度对应的delay_work

第二步:delay_work处理函数

第三步:出口函数

注销delay_work

代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>//定义delay_work核心结构体对象
static struct delayed_work work_lis;//delay_work处理函数
void xxx_workfunc(struct work_struct *work)
{//输出一下对应的行号printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); 
}//静态定义
DECLARE_DELAYED_WORK(work_lis,xxx_workfunc);//模块入口函数
static int __init xxx_init(void)
{//调度schedule_delayed_work(&work_lis,msecs_to_jiffies(3000));printk("schedule_delayed_work success\r\n");return 0;
}//模块的出口函数
static void __exit xxx_exit(void)
{//取消对应工作对象cancel_delayed_work_sync(&work_lis);printk("cancel_delayed_work_sync success\r\n");
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

 

六、按键消抖的实现

思路1

中断顶半部 + 定时器 (抖动)   

卡死的原因:定时器与中断的优先级别都很高,由于按键的抖动多次触发了中断,并且也进行了定时,此时内核无法判断优先执行哪个,从而导致内核崩溃。

分析思路:应用程序app + 中断控制

应用层代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//调用一下open函数fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//获取驱动层硬件的电平状态read(fd,r_buf,1);//避免重复显示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//标志,类似于第一次开机//判断获取到的电平状态  0-按键按下  1-按键抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//关闭对应的文件描述符close(fd);   
}

驱动层代码:

//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>char k_buf[2]={0};struct key_init{int num;			//序号int gpio;			//引脚编号char name[10];		//引脚名称int irq;			//中断号unsigned long flag;//中断的触发方式struct timer_list list_timer;  //定时器核心结构体};//声明变量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING},{},
};//超时处理函数
static void xxx_function(unsigned long data)
{int val;//获取按键的电平状态-gpio_get_value()val = gpio_get_value(keys[0].gpio);//按键按下-0 按键抬起-1printk("val:%d\r\n",val);//将获取到的电平值,保存到k_buf里面k_buf[0] = val + '0';//转为字符printk("data:%ld\r\n",data);}//中断服务函数
static irqreturn_t xxx_key_handler(int irq, void *dev)
{struct key_init*p = (struct key_init*)dev;printk("p->gpio=%d,p->name=%s\r\n",p->gpio,p->name);//设置到期时间//keys[0].list_timer.expires = jiffies + msecs_to_jiffies(30);//注册指定的timer并启动//add_timer(&keys[0].list_timer);//重新启动定时器mod_timer(&(p->list_timer), jiffies+msecs_to_jiffies(20));printk("add success\r\n");return IRQ_HANDLED;
}//定义一个xxx_read---read-读操作实现
//应用层的读函数,对应驱动中的读函数//1-在驱动层获取对应引脚状态//2-将对应的引脚状态给用户层
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{	int res;//将获取到的数据上传到应用层->cope_to_userres = copy_to_user(buf,k_buf,1);if(res == 0){printk("cope_to_user success\r\n");}return 0;
}//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("Goodbye\r\n");printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);return 0;
}// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{.read=xxx_read,.release=xxx_release,
};//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{.minor=255, // 次设备号.name="key_misc",// 设备名称.fops=&pfop,//指向文件操作方法集合的指针};//入口函数
static int __init hello_init(void)
{int ret;//1-检查给定的GPIO编号是否有效ret = gpio_is_valid(keys[0].gpio);if(ret == 1){printk("gpio_is_valid success\r\n");}//2-注册指定的GPIO引脚资源ret = gpio_request(keys[0].gpio,keys[0].name);if(ret == 0){printk("gpio_request success\r\n");}//3-将指定的GPIO引脚设置为输入模式ret = gpio_direction_input(keys[0].gpio);if(ret == 0){printk("gpio_direction_input set success\r\n");}//4-获取指定的GPIO的中断编号keys[0].irq = gpio_to_irq(keys[0].gpio);printk("irq:%d\r\n",keys[0].irq);//5-注册中断,绑定对应的中断服务函数ret=request_irq(keys[0].irq, xxx_key_handler, keys[0].flag,keys[0].name,&keys[0]);if(ret < 0){printk("error request_irq!\r\n");}else {printk("request_irq success\r\n");}//6-动态初始化定时器setup_timer(&keys[0].list_timer,xxx_function,666);//7-注册杂项设备驱动模型ret=misc_register(&pdevc);if(ret < 0) {printk("error misc_register!\r\n");}else {printk("misc_register success\r\n");}return 0;
}//出口函数
static void __exit hello_exit(void)
{int ret;//注销定时器ret = del_timer_sync(&keys[0].list_timer);if(ret == 0){printk("del success\r\n");}//释放指定中断线free_irq(keys[0].irq,NULL);//释放指定的GPIOgpio_free(keys[0].gpio);//注销杂项设备misc_deregister(&pdevc);printk("misc_deregister success\r\n");
}//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

现象:

思路2

中断顶半部 + 中断底半部tasklet + 延时mdelay

应用层代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//调用一下open函数fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//获取驱动层硬件的电平状态read(fd,r_buf,1);//避免重复显示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//标志,类似于第一次开机//判断获取到的电平状态  0-按键按下  1-按键抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//关闭对应的文件描述符close(fd);   
}

驱动层代码:

//死等 + 中断顶半部 + 中断底半部tasklet(消抖)
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/delay.h>char k_buf[2]={0};struct key_init{int num;			//序号int gpio;			//引脚编号char name[10];		//引脚名称int irq;			//中断号unsigned long flag;//中断的触发方式struct tasklet_struct list_task;  //tasklet核心结构体};//声明变量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING},{},
};//处理函数
static void xxx_function(unsigned long data)
{int val;//消抖---占用CPU->再次触发也不会执行mdelay(20);//获取按键的电平状态-gpio_get_value()val = gpio_get_value(keys[0].gpio);//按键按下-0 按键抬起-1printk("val:%d\r\n",val);//将获取到的电平值,保存到k_buf里面k_buf[0] = val + '0';//转为字符printk("data:%ld\r\n",data);}//中断服务函数
static irqreturn_t xxx_key_handler(int irq, void *dev)
{struct key_init*p = (struct key_init*)dev;printk("p->gpio=%d,p->name=%s\r\n",p->gpio,p->name);//调度指定的tasklet---即刻执行任务函数tasklet_schedule(&keys[0].list_task);return IRQ_HANDLED;
}//定义一个xxx_read---read-读操作实现
//应用层的读函数,对应驱动中的读函数//1-在驱动层获取对应引脚状态//2-将对应的引脚状态给用户层
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{	int res;//将获取到的数据上传到应用层->cope_to_userres = copy_to_user(buf,k_buf,1);if(res == 0){printk("cope_to_user success\r\n");}return 0;
}//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("Goodbye\r\n");printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);return 0;
}// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{.read=xxx_read,.release=xxx_release,
};//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{.minor=255, // 次设备号.name="key_misc",// 设备名称.fops=&pfop,//指向文件操作方法集合的指针};//入口函数
static int __init hello_init(void)
{int ret;//1-检查给定的 GPIO编号是否有效ret = gpio_is_valid(keys[0].gpio);if(ret == 1){printk("gpio_is_valid success\r\n");}//2-注册指定的GPIO引脚资源ret = gpio_request(keys[0].gpio,keys[0].name);if(ret == 0){printk("gpio_request success\r\n");}//3-将指定的GPIO引脚设置为输入模式ret = gpio_direction_input(keys[0].gpio);if(ret == 0){printk("gpio_direction_input set success\r\n");}//4-获取对应的中断编号	keys[0].irq=gpio_to_irq(keys[0].gpio);printk("keys[0].irq=%d\r\n",keys[0].irq);//5-注册中断服务函数	ret=request_irq(keys[0].irq,xxx_key_handler,keys[0].flag,keys[0].name,&keys[0]);if (ret==0) {printk(" request_irq success\n");}//6-动态初始化tasklettasklet_init(&keys[0].list_task,xxx_function,666);//7-注册杂项设备驱动模型ret=misc_register(&pdevc);if(ret < 0) {printk("error misc_register!\r\n");}else {printk("misc_register success\r\n");}return 0;
}//出口函数
static void __exit hello_exit(void)
{//取消调度指定的tasklettasklet_kill(&keys[0].list_task);//释放中断free_irq(keys[0].irq, &keys[0]);printk("IRQ freed\n");//释放GPIOgpio_free(keys[0].gpio);printk("GPIO freed\n");//注销杂项设备misc_deregister(&pdevc);printk("misc_deregister success\r\n");
}//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

现象:

思路3

中断顶半部 + 中断底半部work + 睡眠msleep

应用层代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//调用一下open函数fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//获取驱动层硬件的电平状态read(fd,r_buf,1);//避免重复显示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//标志,类似于第一次开机//判断获取到的电平状态  0-按键按下  1-按键抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//关闭对应的文件描述符close(fd);   
}

驱动层代码:

// 中断顶半部 + 中断底半部work
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/workqueue.h>char k_buf[2]={0};struct key_init{int num;			//序号int gpio;			//引脚编号char name[10];		//引脚名称int irq;			//中断号unsigned long flag;//中断的触发方式struct work_struct work_lis; //work核心数据结构};//声明变量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING},{},
};//处理函数
static void xxx_workfunc(struct work_struct *work)
{int val;//延时函数msleep(20);//获取按键的电平状态-gpio_get_value()val = gpio_get_value(keys[0].gpio);//按键按下-0 按键抬起-1printk("val:%d\r\n",val);//将获取到的电平值,保存到k_buf里面k_buf[0] = val + '0';//转为字符}//中断服务函数
static irqreturn_t xxx_key_handler(int irq, void *dev)
{struct key_init*p = (struct key_init*)dev;printk("p->gpio=%d,p->name=%s\r\n",p->gpio,p->name);//调度schedule_work(&keys[0].work_lis);printk("schedule_work success\r\n");return IRQ_HANDLED;
}//定义一个xxx_read---read-读操作实现
//应用层的读函数,对应驱动中的读函数//1-在驱动层获取对应引脚状态//2-将对应的引脚状态给用户层
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{	int res;//将获取到的数据上传到应用层->cope_to_userres = copy_to_user(buf,k_buf,1);if(res == 0){printk("cope_to_user success\r\n");}return 0;
}//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("Goodbye\r\n");printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);return 0;
}// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{.read=xxx_read,.release=xxx_release,
};//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{.minor=255, // 次设备号.name="key_misc",// 设备名称.fops=&pfop,//指向文件操作方法集合的指针};//入口函数
static int __init hello_init(void)
{int ret;//1-检查给定的 GPIO编号是否有效ret = gpio_is_valid(keys[0].gpio);if(ret == 1){printk("gpio_is_valid success\r\n");}//2-注册指定的GPIO引脚资源ret = gpio_request(keys[0].gpio,keys[0].name);if(ret == 0){printk("gpio_request success\r\n");}//3-将指定的GPIO引脚设置为输入模式ret = gpio_direction_input(keys[0].gpio);if(ret == 0){printk("gpio_direction_input set success\r\n");}//4-获取对应的中断编号	keys[0].irq=gpio_to_irq(keys[0].gpio);printk("irq=%d\r\n",keys[0].irq);//5-注册中断服务函数	ret=request_irq(keys[0].irq,xxx_key_handler,keys[0].flag,keys[0].name,&keys[0]);if (ret==0) {printk(" request_irq success\n");}//6-初始化--动态INIT_WORK(&keys[0].work_lis,xxx_workfunc);//7-注册杂项设备驱动模型ret=misc_register(&pdevc);if(ret < 0) {printk("error misc_register!\r\n");}else {printk("misc_register success\r\n");}return 0;
}//出口函数
static void __exit hello_exit(void)
{//取消对应工作对象cancel_work_sync(&keys[0].work_lis);//释放中断free_irq(keys[0].irq, &keys[0]);printk("IRQ freed\n");//释放GPIOgpio_free(keys[0].gpio);printk("GPIO freed\n");//注销杂项设备misc_deregister(&pdevc);printk("misc_deregister success\r\n");
}
//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

现象:

 

思路4

中断顶半部 + 中断底半部delay_work+延时mdelay

注意:调度时延时时间需要设置为0

应用层代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>int main(int argc,char *argv[])
{int fd;char r_buf[2] = {'1',0};char pre[2] = {'1',0};//调用一下open函数fd=open("/dev/key_misc",O_RDWR);if(fd > 0){printf("open success fd=%d\r\n",fd);}while(1){//获取驱动层硬件的电平状态read(fd,r_buf,1);//避免重复显示if(r_buf[0] != pre[0]){pre[0] = r_buf[0];//标志,类似于第一次开机//判断获取到的电平状态  0-按键按下  1-按键抬起if(r_buf[0] == '0'){printf("key down\r\n");}else if(r_buf[0] == '1'){printf("key up\r\n");}}}//关闭对应的文件描述符close(fd);   
}

驱动层代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
//定义一个key引脚相关的结构体
struct key_init{int num;            //序号int gpio;           //引脚编号char name[10];      //引脚名称int irq;            //中断号unsigned long flags;//中断的触发方式struct delayed_work work_lis;;  //中断底半部核心结构体--延时的工作队列
};//声明变量
static struct key_init keys[]={{0,5,"key0",0,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING},{},
};//定义一个k_buf的数组
static char k_buf[2]={0};//work处理函数
static void xxx_workfunc(struct work_struct *work)
{//获取对应引脚的电平状态int s;//延时函数msleep(20);s=gpio_get_value(keys[0].gpio);  //按下按键的时候,对应的引脚电平是低电平//输出观察一下printk("s=%d\r\n",s);//将对应的s里面的值,保存到k_buf里面k_buf[0]= s + '0';//输出观察一下printk("k_buf[0]=%c\r\n",k_buf[0]);printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); }//中断处理函数
static irqreturn_t xxx_handler_key(int irq, void *dev)
{//接收一下,传递进来的结构体对象struct key_init *p =(struct key_init *)dev;//输出观察一下printk("p->gpio=%d , p->name=%s\r\n",p->gpio,p->name);//启动中断底半部--调用对应的workschedule_delayed_work(&keys[0].work_lis,msecs_to_jiffies(0));//提示一下printk("schedule_work success\r\n");return IRQ_HANDLED;
}//定义一个xxx_read函数
//1、利用for循环获取对应引脚状态
//2、将对应的引脚状态给用户层
static ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{    int ret;printk("file:%s,name:%s,line:%d\r\n",__FILE__,__FUNCTION__,__LINE__); //将对应的k_buf里面保存的数据上报给appret=copy_to_user(buf,k_buf,1);printk("copy_to_user success\r\n");return 0;
}//定义一个xxx_release
static int xxx_release(struct inode *pnode, struct file *pfile)
{printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__); return 0;
}//定义一个文件结合
static struct file_operations pfop ={.read = xxx_read,.release =xxx_release,
};//定义核心结构体对象
static struct miscdevice pdevc ={.minor = 255,.name  = "key_misc",.fops  = &pfop,
};//入口函数
static int __init xxx_init(void)
{   int ret;//判断对应的引脚是否合法--对应的引脚是否被占用ret=gpio_is_valid(keys[0].gpio);printk("ret=%d\r\n",ret);//注册对应的gpio口ret=gpio_request(keys[0].gpio,keys[0].name);printk("ret=%d\r\n",ret);//获取对应的中断编号	keys[0].irq=gpio_to_irq(keys[0].gpio);printk("keys[0].irq=%d\r\n",keys[0].irq);//注册中断APIret=request_irq(keys[0].irq,xxx_handler_key,keys[0].flags,keys[0].name,&keys[0]);if(ret == 0){printk("request_irq success\r\n");}//初始化一下--动态//INIT_WORK(&keys[0].work_lis,xxx_workfunc);INIT_DELAYED_WORK(&keys[0].work_lis,xxx_workfunc);//5--注册杂项设备--注册函数APIret=misc_register(&pdevc);if(ret == 0){printk("misc_register success\r\n");}return 0;
}//出口函数
static void __exit xxx_exit(void)
{//移除对应的中断底半部cancel_delayed_work_sync(&keys[0].work_lis);printk("cancel_work_sync success\r\n");//释放中断
free_irq(keys[0].irq,&keys[0]);//释放对应的GPIO口gpio_free(keys[0].gpio);//注销函数misc_deregister(&pdevc);printk("misc_deregister success\r\n");	}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/86037.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/86037.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端跨域解决方案(7):Node中间件

1 Node 中间件核心 1.1 为什么开发环境需要 Node 代理&#xff1f; 在前端开发中&#xff0c;我们常遇到&#xff1a;前端运行在localhost:3000&#xff0c;后端 API 在localhost:4000&#xff0c;跨域导致请求失败。而传统解决方案有以下局限性&#xff1a; 修改后端 CORS 配…

iwebsec靶场-文件上传漏洞

01-前端JS过滤绕过 1&#xff0c;查看前端代码对文件上传的限制策略 function checkFile() { var file document.getElementsByName(upfile)[0].value; if (file null || file "") { alert("你还没有选择任何文件&a…

GitHub 趋势日报 (2025年06月23日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 390 suna 387 system-prompts-and-models-of-ai-tools 383 Web-Dev-For-Beginners…

告别水印烦恼,一键解锁高清无痕图片与视频!

在这个数字化飞速发展的时代&#xff0c;无论是设计小白还是创意达人&#xff0c;都可能遇到这样的困扰&#xff1a;心仪的图片或视频因水印而大打折扣&#xff0c;创意灵感因水印而受限。别急&#xff0c;今天就为大家带来几款神器&#xff0c;让你轻松告别水印烦恼&#xff0…

LangChain4j在Java企业应用中的实战指南:构建RAG系统与智能应用-2

LangChain4j在Java企业应用中的实战指南&#xff1a;构建RAG系统与智能应用-2 开篇&#xff1a;LangChain4j框架及其在Java生态中的定位 随着人工智能技术的快速发展&#xff0c;尤其是大语言模型&#xff08;Large Language Models, LLMs&#xff09;的广泛应用&#xff0c;…

Cola StateMachine 的无状态(Stateless)特性详解

Cola StateMachine 的无状态&#xff08;Stateless&#xff09;特性详解 在现代分布式系统中&#xff0c;无状态设计是构建高可用、可扩展服务的关键原则之一。Cola StateMachine 作为一款轻量级的状态机框架&#xff0c;通过其独特的设计理念实现了良好的无状态特性。本文将深…

使用事件通知来处理页面回退时传递参数和赋值问题

背景。uniapp开发微信小程序。在当前页面需要选择条件&#xff0c;如选择城市。会打开新的页面。此时选择之后需要关闭页面回到当初的页面。但问题出现了。onLoad等事件是不会加载的。相关链接。uniapp页面通讯说明使用事件通知来处理页面回退时传递参数和赋值问题 页面之间的…

腾讯云COS“私有桶”下,App如何安全获得音频调用流程

流程图 #mermaid-svg-Phy4VCltBRZ90UH8 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Phy4VCltBRZ90UH8 .error-icon{fill:#552222;}#mermaid-svg-Phy4VCltBRZ90UH8 .error-text{fill:#552222;stroke:#552222;}#me…

基于深度学习的侧信道分析(DLSCA)Python实现(带测试)

一、DLSCA原理介绍 基于深度学习的侧信道分析(DLSCA)是一种结合深度神经网络与侧信道分析技术的密码分析方法。该方法利用深度学习模型从能量消耗、电磁辐射等侧信道信息中提取与密钥相关的特征模式。相比传统分析方法&#xff0c;DLSCA能够自动学习复杂的特征关系&#xff0c…

云原生 CAD 让制造业设计协同更便捷

随着互联网、云计算技术的突飞猛进&#xff0c;CAD向着网络化、协同化的方向快速发展&#xff0c;云CAD软件逐渐映入人们的眼帘。云原生CAD不仅打破了传统CAD软件对硬件配置的依赖&#xff0c;更以数据驱动的协同创新模式&#xff0c;重塑了制造业的产品研发流程与组织协作形态…

Docker容器核心操作指南:`docker run`参数深度解析

技术聚焦 作为容器化技术的起点&#xff0c;docker run命令承担着90%的容器创建工作。其关键参数-d&#xff08;后台模式&#xff09;与-it&#xff08;交互模式&#xff09;的合理运用&#xff0c;直接影响容器行为模式与运维效率。本文将深度拆解两大模式的应用场景与…

基于单片机的语音控制设计(论文)

摘要 自然语音作为人机交互在目前得以广泛的应用以及极大的发展前景。该设计介绍了基于非指定人语音芯片LD3320的语音控制器结构及其实现语音控制的方法。该语音控制器利用STM32F103C8T6单片机作为主要控制器&#xff0c;控制芯片对输入的进行语音识别并处理&#xff0c;根据语…

【论文阅读 | CVPRW 2023 |CSSA :基于通道切换和空间注意力的多模态目标检测】

论文阅读 | CVPRW 2023 |CSSA &#xff1a;基于通道切换和空间注意力的多模态目标检测 1.摘要&&引言2.方法2.1 框架概述2.2 通道切换通道注意力2.3 空间注意力 3. 实验3.1 实验设置3.1.1 数据集3.1.2 实现细节3.1.3 评估指标 3.2 对比研究3.2.1 定量结果3.2.2 定性结果…

《前端资源守卫者:SRI安全防护全解析》

SRI&#xff08;子资源完整性&#xff09;作为守护前端安全的隐形盾牌&#xff0c;以精妙的技术设计构建起资源验证防线。深入理解其工作逻辑与配置方法&#xff0c;是每位前端开发者筑牢应用安全的必修课。 SRI的核心价值&#xff0c;在于为外部资源打造独一无二的“数字身份…

项目需求评审报告参考模板

该文档是需求评审报告模板 内容涵盖评审基础信息,如项目名称、评审时间、地点、级别、方式等;包含评审签到表,记录角色、部门、职务、姓名等信息;还有评审工作量统计相关内容;以及评审问题跟踪表,记录问题描述、状态、解决人及时限等,还附有填表说明,对评审适用范围、工…

从依赖进口到自主创新:AI 电子设计系统如何重塑 EDA 全流程

EDA全称是Electronic Design Automation&#xff0c;即电子设计自动化&#xff0c;是利用计算机软件完成电路设计、仿真、验证等流程的设计工具&#xff0c;贯穿于芯片和板级电路设计、制造、测试等环节&#xff0c;是不可或缺的基础设计工具。 EDA与电子材料、装备是电子信…

前端工程化之微前端

微前端 微前端基本知识主要的微前端框架iframe优点&#xff1a;缺点&#xff1a; single-spa示例主应用spa-root-config.jsmicrofrontend-layout.htmlindex.ejs 子应用spa-react-app2.jsroot.component.js 修改路由spa-demo/microfrontend-layout.htmlspa-demo/react-app1/webp…

MemcacheRedis--缓存服务器理论

Memcached/redis是高性能的分布式内存缓存服务器,通过缓存数据库查询结果&#xff0c;减少数据库访问次数&#xff0c;以提高动态Web等应用的速度、 提高可扩展性。 缓存服务器作用: 加快访问速度 ,缓解数据库压力 1. memcached&#xff08;单节点在用&#xff09; 1.1 特点 1…

【stm32】标准库学习——I2C

目录 一、I2C 1.I2C简介 2.MPU6050参数 3.I2C时序基本单元 二、I2C外设 1.I2C外设简介 2.配置I2C基本结构 3.初始化函数模板 4.常用函数 一、I2C 1.I2C简介 本节课使用的是MPU6050硬件外设 2.MPU6050参数 3.I2C时序基本单元 这里发送应答是指主机发送&#xff0c;即…

HSA22HSA29美光固态芯片D8BJVC8BJW

HSA22HSA29美光固态芯片D8BJVC8BJW 美光固态芯片D8BJVC8BJW系列&#xff1a;技术革新与行业应用深度解析 一、技术解析&#xff1a;核心架构与创新突破 美光D8BJVC8BJW系列固态芯片&#xff08;如MT29F8T08EQLEHL5-QAES:E、MT29F512G08CUCABH3-12Q等&#xff09;的技术竞争力…