首页 > 系统相关 >linux中断

linux中断

时间:2023-08-13 17:11:16浏览次数:50  
标签:struct 中断 irq work workqueue linux tasklet

1,申请中断API函数request_irq()

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

irq:要申请中断的中断号。
handler:中断处理函数。
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,这里我们介绍几个常用的中断标志。

标志 描述
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话, request_irq 函数的 dev 参数就是唯一区分他们的标志
IRQF_ONESHOT 单次中断,中断执行一次就结束
IRQF_TRIGGER_NONE 无触发
IRQF_TRIGGER_RISING 上升沿触发
IRQF_TRIGGER_FALLING 下降沿触发
IRQF_TRIGGER_HIGH 高电平触发
IRQF_TRIGGER_LOW 低电平触发
返回值: 0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。
request_irq()函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 。

2,free_irq()

 
/**
 *    free_irq - free an interrupt allocated with request_irq
 *    @irq: Interrupt line to free
 *    @dev_id: Device identity to free
 *
 *    Remove an interrupt handler. The handler is removed and if the
 *    interrupt line is no longer in use by any driver it is disabled.
 *    On a shared IRQ the caller must ensure the interrupt is disabled
 *    on the card it drives before calling this function. The function
 *    does not return until any executing interrupts for this IRQ
 *    have completed.
 *
 *    This function must not be called from interrupt context.
 */
void free_irq(unsigned int irq, void *dev_id)
{
    struct irq_desc *desc = irq_to_desc(irq);
 
    if (!desc)
        return;
 
    chip_bus_lock(irq, desc);
    kfree(__free_irq(irq, dev_id));
    chip_bus_sync_unlock(irq, desc);
}

unsigned int  irq:要卸载的中断号

void  *dev_id:这个是要卸载的中断action下的哪个服务函数

编程注意:

1.如果是采用非共享方式注册中断,则request_irq和free的最后一个参数都要为NULL。

2.如果采用共享中断方式,所有使用request_irq注册的中断时flags都要加上IRQF_SHARED这个共享参数,表明其实共享中断。

3.对于共享中断,每一个申请共享的中断,申请和释放时都要给request_irq和free_irq的最后一个参数dev和id_dev传递一个指针,将来来中断的时候,将会传递这个指针到每个中断函数中,而中断函数就可以用来区分到底是不是它的中断,是则执行,不是则判断后直接退出中断处理函数即可。同时在free_irq时也会使用这个指针,查找这个贡献中断链表上了所有注册的irq,只有在这个指针能对的上的时候,才会删除它所在的链表节点(如果是最后一个节点还要释放该中断)。所在在编写中断处理函数时该指针必须是唯一的,通常传的这个指针是该设备结构体的地址,这个每个设备不一样所以肯定是唯一的。

下面这几句是我查找资料的发现说的比较透彻的,我先引用一下,过几天我抽时间在代码层面分析一下中断的实现机制。

原来对于计算机设备比较少的时候,可能一个中断线好可以对应一个中断处理程序(非共享中断线),这时候参数4为NULL,没有任何用,但随着计算机设备的增加,一个中断线号对应一个中断处理程序已经不太现实,这个时候就使用了共享的中断线号,多个设备使用同一个中断线号,同一个中断设备线号的所有处理程序链接成一个链表,这样当在共享中断线号的方式下一个中断产生的时候,就要遍历其对应的处理程序链表,但这个中断是由使用同一个中断线号的多个设备中间的一个产生的,不可能链表里面的所有处理程序都调用一遍吧,呵呵,这个时候就该第四个参数派上用场了。

  因为多个设备共享同一个中断线号,当中断产生的时候到底是那一个设备产生的中断呢,这个就取决于第四个参数dev_id,这个参数必须是唯一的,也就是能区分到底是那个设备产生的中断,而且从第二个参数可以看出来,这个参数被传入中断处理程序(第二个参数),可以这么理解,当中断产生的时候,如果是共享的中断线号,则对应链表的所有中断处理程序都被调用,不过在每个中断处理程序的内部首先检查(参数信息以及设备硬件的支持)是不是这个中断处理程序对应的设备产生的中断,如果不是,立即返回,如果是,则处理完成,如果链表中没有一个是,则说明出现错误。

 

3,使能和屏蔽中断

1)屏蔽指定中断源

void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);
disable_irq_nosync()与 disable_irq()的区别在于前者立即返回,而后者等待目前的中断处理完成

2)屏蔽所有中断---< asm/system.h >中

void local_irq_save(unsigned long flags);
void local_irq_disable(void);

对 local_irq_save的调用将把当前中断状态保存到flags中,然后禁用当前处理器上的中断发送。注意,flags 被直接传递, 而不是通过指针来传递。local_irq_disable不保存状态而关闭本地处理器上的中断发送; 只有我们知道中断并未在其他地方被禁用的情况下,才能使用这个版本。

void local_irq_restore(unsigned long flags); 
void local_irq_enable(void);

local_irq_restore将local_irq_save保存的flags状态值恢复, 而local_irq_enable无条件打开中断. 与disable_irq不同, local_irq_disable不会维护对多次的调用的跟踪。 如果调用链中有多个函数需要禁止中断, 应该使用local_irq_save。

4,底半部机制

主要包括tasklet,工作队列,软中断,线程化irq

4.1 tasklet

tasklet 是通过软中断实现的, 所以它本身也是软中断。 软中断用轮询的方式处理, 假如正好是最后一种中断, 则必须循环完所有的中断类型, 才能最终执行对应的处理函数。 为了提高中断处理数量, 顺道改进处理效率, 于是产生了 tasklet 机制。 tasklet 采用无差别的队列机制, 有中断时才执行, 免去了循环查表之苦, tasklet 机制的优点: 无类型数量限制, 效率高, 无需循环查表, 支持 SMP 机制, 一种特定类型的 tasklet 只能运行在一个 CPU 上, 不能并行, 只能串行执行。 多个不同类型的 tasklet 可以并行在多个CPU 上。 软中断是静态分配的, 在内核编译好之后, 就不能改变。 但 tasklet 就灵活许多, 可以在运行时改变(比如添加模块时) 。

struct tasklet_struct
{ 
struct tasklet_struct *next;  /* 下一个 tasklet */
unsigned long state;          /* tasklet 状态 */
atomic_t count;               /* 计数器, 记录对 tasklet 的引用数 */
void (*func)(unsigned long);  /* tasklet 执行的函数 */
unsigned long data;           /* 函数 func 的参数 */
};

next: 链表中的下一个 tasklet, 方便管理和设置 tasklet;

state: tasklet 的状态。

count: 表示 tasklet 是否出在激活状态, 如果是 0, 就处在激活状态, 如果非 0, 就处在非激活状态---

void (*func)(unsigned long): 结构体中的 func 成员是 tasklet 的绑定函数, data 是它唯一的参数。

date: 函数执行的时候传递的参数。

如果要使用 tasklet, 必须先定义一个 tasklet, 然后使用 tasklet_init 函数初始化 tasklet, taskled_init 函数原型如下(动态初始化 tasklet):

void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long),unsigned long data);
t---要初始化的 tasklet
func---tasklet 的处理函数
data---要传递给 func 函数的参数

也可以使用宏 DECLARE_TASKLET 一次性完成 tasklet 的定义和初始化, DECLARE_TASKLET 定义在include/linux/interrupt.h 文件中, 定义如下:

DECLARE_TASKLET(name, func, data)

其中 name 为要定义的 tasklet 名字, 这个名字就是一个 tasklet_struct 类型的变量, func 就是tasklet 的处理函数, data 是传递给 func 函数的参数。
在需要调度 tasklet 的时候引用一个 tasklet_schedule() 函数就能使系统在适当的时候进行调度运行,该函数原型为如下所示:

void tasklet_schedule(struct tasklet_struct *t)
t--要调度的 tasklet, 也就是 DECLARE_TASKLET 宏里面的 name。

杀死 tasklet 使用 tasklet_kill 函数,函数原型如下表所示:(这个函数会等待 tasklet 执行完毕, 然后再将它移除。 该函数可能会引起休眠, 所以要禁止在
中断上下文中使用。)

tasklet_kill(struct tasklet_struct *t)
t--要删除的 tasklet

tasklet参考步骤:

 1 /* 定义 taselet */
 2 struct tasklet_struct testtasklet;
 3 /* tasklet 处理函数 */
 4 void testtasklet_func(unsigned long data)
 5 { 
 6 /* tasklet 具体处理内容 */
 7 } 
 8 /* 中断处理函数 */
 9 irqreturn_t test_handler(int irq, void *dev_id)
10 { 
11 ......
12 /* 调度 tasklet */
13 tasklet_schedule(&testtasklet);
14 ......
15 } 
16 /* 驱动入口函数 */
17 static int __init xxxx_init(void)
18 { 
19 ......
20 /* 初始化 tasklet */
21 tasklet_init(&testtasklet, testtasklet_func, data);
22 /* 注册中断处理函数 */
23 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
24 ......
25 }

总结一下基本步骤为:
步骤一: 定义一个 tasklet 结构体
步骤二: 动态初始化 tasklet
步骤三: 编写 tasklet 绑定的函数
步骤四: 在中断上文调用 tasklet
步骤五: 卸载模块的时候删除 tasklet

 

4.2 工作队列

工作队列的执行上下文是内核线程,因此可以调度和睡眠,解决了如果软中断和tasklet执行时间过长会导致系统实时性下降等问题。

 

(1-1)work_struct工作

linux内核中使用work_struct结构体来表示一个工作,如下定义(/inlcude/linux/workqueue.h):

struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};
(1-2)workqueue工作队列

把工作(包括该工作任务的执行回调函数)添加到一个队列,称为workqueue,即工作队列,然后通过worker-pool中的内核工作线程(worker)去执行这个回调函数。工作队列使用workqueue_struct结构体来表示,定义如下(/kernel/workqueue.c):

 1 struct workqueue_struct {
 2     struct list_head    pwqs;        /* WR: all pwqs of this wq */
 3     struct list_head    list;        /* PR: list of all workqueues */
 4 
 5     struct mutex        mutex;        /* protects this wq */
 6     int            work_color;    /* WQ: current work color */
 7     int            flush_color;    /* WQ: current flush color */
 8     atomic_t        nr_pwqs_to_flush; /* flush in progress */
 9     struct wq_flusher    *first_flusher;    /* WQ: first flusher */
10     struct list_head    flusher_queue;    /* WQ: flush waiters */
11     struct list_head    flusher_overflow; /* WQ: flush overflow list */
12 
13     struct list_head    maydays;    /* MD: pwqs requesting rescue */
14     struct worker        *rescuer;    /* I: rescue worker */
15 
16     int            nr_drainers;    /* WQ: drain in progress */
17     int            saved_max_active; /* WQ: saved pwq max_active */
18 
19     struct workqueue_attrs    *unbound_attrs;    /* WQ: only for unbound wqs */
20     struct pool_workqueue    *dfl_pwq;    /* WQ: only for unbound wqs */
21 
22 #ifdef CONFIG_SYSFS
23     struct wq_device    *wq_dev;    /* I: for sysfs interface */
24 #endif
25 #ifdef CONFIG_LOCKDEP
26     struct lockdep_map    lockdep_map;
27 #endif
28     char            name[WQ_NAME_LEN]; /* I: workqueue name */
29 
30     /*
31      * Destruction of workqueue_struct is sched-RCU protected to allow
32      * walking the workqueues list without grabbing wq_pool_mutex.
33      * This is used to dump all workqueues from sysrq.
34      */
35     struct rcu_head        rcu;
36 
37     /* hot fields used during command issue, aligned to cacheline */
38     unsigned int        flags ____cacheline_aligned; /* WQ: WQ_* flags */
39     struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
40     struct pool_workqueue __rcu *numa_pwq_tbl[]; /* FR: unbound pwqs indexed by node */
41 };
(1-3)worker工作者线程

 linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作,linux 内核使用worker 结构体表示工作者线程,worker 结构体定义如下(/kernel/workqueue_internal.h):

 1 struct worker {
 2     /* on idle list while idle, on busy hash table while busy */
 3     union {
 4         struct list_head    entry;    /* L: while idle */
 5         struct hlist_node    hentry;    /* L: while busy */
 6     };
 7 
 8     struct work_struct    *current_work;    /*当前正在处理的work */
 9     work_func_t        current_func;    /* 当前正在执行的work回调函数 */
10     struct pool_workqueue    *current_pwq; /* 当前work所属的pool_workqueue*/
11     bool            desc_valid;    /* ->desc is valid */
12     struct list_head    scheduled;    /* 所有被调度并正准备执行的work都将加入到该链表中*/
13 
14     /* 64 bytes boundary on 64bit, 32 on 32bit */
15 
16     struct task_struct    *task;        /* 该工作线程的task_struct  */
17     struct worker_pool    *pool;        /* 该工作线程所属的worker_pool */
18                         /* L: for rescuers */
19     struct list_head    node;        /* worker挂入的 链表 pool->workers */
20                         /* A: runs through worker->node */
21 
22     unsigned long        last_active;    /* L: last active timestamp */
23     unsigned int        flags;        /* X: flags */
24     int            id;        /* 工作线程的id */
25 
26     /*
27      * Opaque string set with work_set_desc().  Printed out with task
28      * dump for debugging - WARN, BUG, panic or sysrq.
29      */
30     char            desc[WORKER_DESC_LEN];
31 
32     /* used only by rescuers to point to the target workqueue */
33     struct workqueue_struct    *rescue_wq;    /* I: the workqueue to rescue */
34 };

workqueue工作队列的使用

每一个worker工作线程中都有一个工作队列,工作线程处理自己工作队列中的所有工作。

在实际开发中,推荐使用默认的workqueue·工作队列,而不是新创建workqueue。使用方法如下:

​ 直接定义一个work_struct结构体变量,然后使用INIT_WORK宏来完成初始化工作,INIT_WORK定义如下:

#define INIT_WORK(_work, _func)

_work表示要初始化的工作,_func是工作对应的处理函数。

也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:

#define DECLARE_WORK(n, f)

 n 表示定义的工作(work_struct),f 表示工作对应的处理函数。和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为schedule_work(),函数原型如下所示:

bool schedule_work(struct work_struct *work)

使用cancel_work_sync()取消一个工作,函数原型如下所示:

bool cancel_work_sync(struct work_struct *work)

当然也可以自己创建一个workqueue,特别是网络子系统、块设备子系统情况下等。具体步骤如下:

使用alloc_workqueue()创建新的workqueue。
使用INIT_WORK()宏声明一个work和该work的回调函数。
使用queue_work()在新的workqueue上调度一个work。
使用flush_workqueue()去flush 工作队列上的所有work。
除此之外,linux内核还提供了一个workqueue机制与timer机制相结合的延时机制—delayed_work

 1 //定义一个工作(work)
 2 static struct work_sturct my_work;
 3 
 4 //定义一个工作处理函数
 5 void my_work_func(struct work_struct *work)
 6 {
 7     /*.......*/
 8 }
 9 
10 
11 //定义中断处理函数
12 irqreturn_t  key_handler(int irq,void *dev_id)
13 {
14     //.........
15     
16     //调度work
17     shcedule_work(&my_work);
18     
19    // ......
20 }
21 
22 /* 驱动入口函数
23 */
24 static int __init my_demo_init(void)
25 {
26     //...
27     
28     //初始化work
29     INIT_WORK(&my_work,my_work_func);
30     
31     //注册中断处理 函数
32     request_irq(xxx_irq,key_handler,0,"xxxx",&xxx_dev);
33     
34     //....
35 }
36 
37 static void __exit my_demo_exit(void)
38 {
39     //执行一些释放操作
40     //....
41 }
42 
43 
44 module_init(my_demo_init);
45 module_exit(my_demo_exit);
46     
47 MODULE_LICENSE("GPL");
48 MODULE_AUTHOR("iriczhao");

 

4.3 软中断--softirq

 

 

 

 

 

 

 

 

参考:request_irq和free_irq的使用_奔跑的小刺猬的博客-CSDN博客

Linux内核API disable_irq|极客笔记 (deepinout.com)

https://blog.csdn.net/yangxueyangxue/article/details/122661049

【linux kernel】linux中断管理—workqueue工作队列_cancel_work_sync_iriczhao的博客-CSDN博客

 

标签:struct,中断,irq,work,workqueue,linux,tasklet
From: https://www.cnblogs.com/zhiminyu/p/17618002.html

相关文章

  • Burp Suite Professional / Community 2023.9 (macOS, Linux, Windows) - Web 应用安
    BurpSuiteProfessional/Community2023.9(macOS,Linux,Windows)-Web应用安全、测试和扫描BurpSuiteProfessional,Test,find,andexploitvulnerabilities.请访问原文链接:https://sysin.org/blog/burp-suite-pro-2023/,查看最新版。原创作品,转载请保留出处。作者......
  • linux 6/7 修改root 密码 (grub界面)
    CentOS6:开机读秒时按任意键进入此画面按e选择kernel再按e在最后打一个空格然后输入1回车再按B进入单用户模式Passwd可以修改密码了CentOS7:开机菜单栏第一行按e,在linux16行尾,加入rd.breakcnotallow=tty0ctrl+x继续启动现在的根是/sysrootmount-oremount,rw/sys......
  • linux安装anaconda
    找到相应的版本:Anaconda与Python版本对应关系_笔记大全_设计学院(python100.com) 我下载的是3.6版本的python找到对应版本的下载链接:Indexof/(anaconda.com) (https://repo.anaconda.com/archive/Anaconda3-5.0.0-Linux-x86_64.sh)终端中输入  wgethttps://repo.ana......
  • linux进阶:设备号
    设计字符设备文件系统调用系统IO的内核处理过程在Linux文件系统管理中,当应用程序调用open函数时,内核会根据文件路径找到文件的索引结点(inode),为文件分配文件描述符和文件对象,并根据打开模式和权限等参数进行相应的操作和设置。  硬件层原理思路:把底层寄存器配置操作放在文......
  • Linux安全加固
    配置口令复杂度:vi/etc/pam.d/system-auth添加:passwordrequisitepam_cracklib.sodifok=3minien=8ucredit=1lcredit=-1dcredit=1#3种类型组合,最短8位设置口令认证失败锁定次数:添加:authrequiredpam_tally.soonerr=faildeny=10unlock_time=300配置口令生存期:是否有以下......
  • Linux 学习十章
    第一章:Linux基础概念Linux的历史与发展Linux发行版及其特点常用的Linux命令行工具和基本操作第二章:文件和目录管理Linux文件系统的层级结构常用的文件和目录操作命令文件权限和所有权管理第三章:文本编辑器和Shell脚本常用的文本编辑器,如Vi或NanoShell脚本编程基础常用的Shell脚本语......
  • Linux MQTT智能家居(温度,湿度,环境监测,摄像头等界面布局设置)
    (文章目录)前言本篇文章来完成另外三个界面的布局设置。这里会使用到feiyangqingyun的一些控件库。一、温度湿度曲线布局TempHumtiy.h:#ifndefTEMPHUMTIY_H#defineTEMPHUMTIY_H#include<QWidget>#include"wavechart.h"namespaceUi{classTempHumtiy;}class......
  • 使用Linux路由功能
    使用Linux路由功能1、开启Linux主机路由配置功能永久开启,更改配置文件vim/etc/sysctl.confnet.ipv4.ip_forward=1默认值为0[root@ketang-test~]#sysctl-p令修改立刻生效临时开启:echo1>/proc/sys/net/ipv4/ip_forward2、临时添加路由条目使用route命令添加的路由,机器......
  • Linux下C语言调用libcurl库获取天气预报信息
    一、概述当前文章介绍如何在Linux(Ubuntu)下使用C语言调用libcurl库获取天气预报的方法。通过HTTPGET请求访问百度天气API,并解析返回的JSON数据,可以获取指定城市未来7天的天气预报信息。二、设计思路【1】使用libcurl库进行HTTPGET请求在代码中包含<curl/curl.h>头文件,以便使用libc......
  • Linux下Shell脚本中比较大小
    数字比较-eq等于,如:if["$a"-eq"$b"]-ne不等于,如:if["$a"-ne"$b"]-gt大于,如:if["$a"-gt"$b"]-ge大于等于,如:if["$a"-ge"$b"]-lt小于,如:if["$a"-lt"$b......