linux中断

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

软中断(Softirq)也是一种传统的底半部处理机制,它的执行时机通常是顶半部返回的时候,tasklet是基于软中断实现的,因此也运行于软中断上下文。

在Linux内核中,用softirq_action结构体表征一个软中断,这个结构体包含软中断处理函数指针和传递给该函数的参数。使用open_softirq()函数可以注册软中断对应的处理函数,而raise_softirq()函数可以触发一个软中断。

软中断和tasklet运行于软中断上下文,仍然属于原子上下文的一种,而工作队列则运行于进程上下文。因此,在软中断和tasklet处理函数中不允许睡眠,而在工作队列处理函数中允许睡眠。

local_bh_disable() 和 local_bh_enable() 是内核中用于禁止和使能软中断及tasklet底半部机制的函数。

内核中采用softirq的地方包括 HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、TASKLET_SOFTIRQ等,一般来说,驱动的编写者不会也不宜直接使用softirq。

 

硬中断、软中断和信号的区别:

  硬中断是外部设备对CPU的中断,软中断是中断底半部的一种处理机制,而信号则是由内核(或其他进程)对某个进程的中断。在设计系统调用的场合,人们也常说通过软中断(例ARM为swi)陷入内核,此时软中断的概念是指由软件指令引发的中断,和我们这个地方所说的softirq是两个完全不同的概念,一个是software,一个是soft。

需要特别说明的是,软中断以及基于软中断的tasklet如果在某段时间内大量出现的话,内核会把后续软中断放入 ksoftirqd 内核线程中执行。总的来说,中断优先级高于软中断,软中断优先级又高于任何一个线程。软中断适度线程化,可以缓解高负载情况下系统的响应。

 

4.4 thread_irq

在内核中除了可以通过request_irq()、devm_request_irq()申请中断以外,还可以通过request_threaded_irq() 和 devm_request_threaded_irq() 申请。这两个函数的原型为:

 

/**
 *    request_threaded_irq - allocate an interrupt line
 *    @irq: Interrupt line to allocate
 *    @handler: Function to be called when the IRQ occurs.
 *          Primary handler for threaded interrupts.
 *          If handler is NULL and thread_fn != NULL
 *          the default primary handler is installed.
 *    @thread_fn: Function called from the irq handler thread
 *            If NULL, no irq thread is created
 *    @irqflags: Interrupt type flags
 *    @devname: An ascii name for the claiming device
 *    @dev_id: A cookie passed back to the handler function
 *
 *    This call allocates interrupt resources and enables the
 *    interrupt line and IRQ handling. From the point this
 *    call is made your handler function may be invoked. Since
 *    your handler function must clear any interrupt the board
 *    raises, you must take care both to initialise your hardware
 *    and to set up the interrupt handler in the right order.
 *
 *    If you want to set up a threaded irq handler for your device
 *    then you need to supply @handler and @thread_fn. @handler is
 *    still called in hard interrupt context and has to check
 *    whether the interrupt originates from the device. If yes it
 *    needs to disable the interrupt on the device and return
 *    IRQ_WAKE_THREAD which will wake up the handler thread and run
 *    @thread_fn. This split handler design is necessary to support
 *    shared interrupts.
 *
 *    Dev_id must be globally unique. Normally the address of the
 *    device data structure is used as the cookie. Since the handler
 *    receives this value it makes sense to use it.
 *
 *    If your interrupt is shared you must pass a non NULL dev_id
 *    as this is required when freeing the interrupt.
 *
 *    Flags:
 *
 *    IRQF_SHARED        Interrupt is shared
 *    IRQF_TRIGGER_*        Specify active edge(s) or level
 *    IRQF_ONESHOT        Run thread_fn with interrupt line masked
 */
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn, unsigned long irqflags,
             const char *devname, void *dev_id);

/**
 *    devm_request_threaded_irq - allocate an interrupt line for a managed device
 *    @dev: device to request interrupt for
 *    @irq: Interrupt line to allocate
 *    @handler: Function to be called when the IRQ occurs
 *    @thread_fn: function to be called in a threaded interrupt context. NULL
 *            for devices which handle everything in @handler
 *    @irqflags: Interrupt type flags
 *    @devname: An ascii name for the claiming device, dev_name(dev) if NULL
 *    @dev_id: A cookie passed back to the handler function
 *
 *    Except for the extra @dev argument, this function takes the
 *    same arguments and performs the same function as
 *    request_threaded_irq().  IRQs requested with this function will be
 *    automatically freed on driver detach.
 *
 *    If an IRQ allocated with this function needs to be freed
 *    separately, devm_free_irq() must be used.
 */
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
                  irq_handler_t handler, irq_handler_t thread_fn,
                  unsigned long irqflags, const char *devname,
                  void *dev_id);

由此可见,它们比request_irq()、devm_request_irq()多了一个参数 thread_fn。用这两个API申请中断的时候,内核会为相应的中断号分配一个对应的内核线程。注意这个线程只针对这个中断号,如果其他中断也通过request_threaded_irq()申请,自然会得到新的内核线程。

参数handler对应的函数执行于中断上下文,thread_fn参数对应的函数则执行于内核线程。如果handler结束的时候,返回值是 IRQ_WAKE_THREAD,内核会调度对应线程执行 thread_fn 对应的函数。

request_threaded_irq() 和 devm_request_threaded_irq() 支持在 irqflags 中设置 IRQF_ONESHOT标记,这样内核会自动帮助我们在中断上下文中屏蔽对应的中断号,而在内核调度 thread_fn 执行后,重新使能该中断号。对于我们无法在上半部清除中断的情况, IRQ_ONESHOT 特别有用,避免了中断服务程序一退出,中断就洪泛的情况。

handler 参数可以设置为NULL,这种情况下,内核会用默认的 irq_default_primary_handler() 代替 handler,并会使用 IRQ_ONESHOT标记。 irq_default_primary_handler() 定义为:

/*
 * Default primary interrupt handler for threaded interrupts. Is
 * assigned as primary handler when request_threaded_irq is called
 * with handler == NULL. Useful for oneshot interrupts.
 */
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
    return IRQ_WAKE_THREAD;
}

 

 

 

 

参考: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博客

Linux中断底半部机制总结 - 闹闹爸爸 - 博客园 (cnblogs.com)

 

posted @ 2023-08-13 17:00  burlingame  阅读(133)  评论(0编辑  收藏  举报