中断:是指CPU在执行程序的过程中,出现了某些突发事件急待处理,CPU必须暂停当前程序的执行,转去处理突发事件,处理完毕后又返回源程序被中断的位置继续执行。
按中断来源分类:
按是否可以屏蔽分类:
根据中断入口跳转方法分类:
在ARM系统中,中断与异常模式有密切的关系:
1、 中断是外部事件引起的异常,由外部设备触发。异常是由CPU内部异常条件触发的。
2、 当中断或异常发生时,CPU会从当前的执行模式(如用户模式或系统模式)切换到一个异常模式。ARM支持7种异常模式:
3、 在异常模式下,CPU会保存当前模式的程序状态,然后跳转到对应的异常入口地址执行异常服务程序。
4、 执行完成后,异常服务程序使用指令SUBS PC, LR, #imm
返回到中断前的模式和程序计数器。
所以中断是通过异常模式来处理的,IRQ和FIQ是两种用于处理中断的异常模式。其他异常模式用于处理其它异常条件。 中断的实际处理入口是通过IRQ或FIQ这两种异常模式的入口来实现的。
\qquad 在ARM多核处理器里最常用的中断控制器是GIC(Generic Interrupt Controller),如下图:
ARM把中断分成以下三种类型:
\qquad 中断控制器(如GIC)负责检测SPI和PPI外设中断,并激活中断的目标异常模式。而SGI直接通过软件指令激活异常模式。
中断的处理流程
中断源(SGI/PPI/SPI) -> 异常模式(FIQ/IRQ/SVC/…) -> 异常入口 -> 中断服务程序 -> 正常模式
中断源通过触发相应的异常模式,异常模式通过异常入口调用中断服务程序进行中断处理,完成后返回正常模式。
exynos4412(contex A9) ,总共支持160个中断,包括软件生成的中断(SGI[15:0],ID[15:0])、专用外围设备中断(PPI[15:0]、ID[31:16])和共享外围设备中断。对于SPI,最多可以提供32* 4=128个中断请求。
具体的中断表如下:(在驱动时涉及到的中断必须查询下表)
\qquad 设备的中断会打断内核进程中的正常调度和运行,系统必然要求中断服务程序能短小精悍,这也是为什么我们之前的内容里有中断上下文中不允许有阻塞出现。但在大多数真实的系统当中,中断要处理完成的工作往往并不会短小,它可能要进行较大量的耗时处理。
\qquad linux内核的中断处理机制中,为了在中断执行时间尽量短 与 中断处理需 完成的工作尽量大之间找到平衡点,linux将中断处理程序分解为两个半部:顶半部(Top Half)和底半部 (Bottom Half)。
\qquad 顶半部用于完成尽量少的比较紧急的功能,并将底半部处理程序挂到该设备的底半部执行队列中去。这样,顶半部执行的速度就会很快,及时返回。
\qquad 底半部则以“普通任务”形态,参与到系统的任务调度中去执行,它需要完成中断事件的长耗时工作,并可以被新的中断打断,这也是底半部和顶半部最大的不同。
\qquad 当然,如果中断处理程序本来要完成的工作就耗时很短,则无需一定要分成两个半部。
底半部的实现机制主要有以下四种:
以上四种机制,分别是利用了内核的不同框架,在实现上有各自不同的函数接口。此处先了解,在后面的章节进行具体的介绍。
中断接口编程的一般流程顺序如下:
include/linux/interrupt.h这个头文件定义了绝大部分与Linux内核中断管理相关的接口,程序员可以通过这些接口对中断进行申请、配置、处理、释放等操作。
request_irq函数声明在linux内核的include/linux/interrupt.h头文件中
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
参数:
flags参数包含以下常用设置:
【触发方式】相关的标志:
返回值:
request_irq函数返回值宏 参考:
#define EPERM 1 /* Operation not permitted /
#define ENOENT 2 / No such file or directory /
#define ESRCH 3 / No such process /
#define EINTR 4 / Interrupted system call /
#define EIO 5 / I/O error /
#define ENXIO 6 / No such device or address /
#define E2BIG 7 / Argument list too long /
#define ENOEXEC 8 / Exec format error /
#define EBADF 9 / Bad file number /
#define ECHILD 10 / No child processes /
#define EAGAIN 11 / Try again /
#define ENOMEM 12 / Out of memory /
#define EACCES 13 / Permission denied /
#define EFAULT 14 / Bad address /
#define ENOTBLK 15 / Block device required /
#define EBUSY 16 / Device or resource busy /
#define EEXIST 17 / File exists /
#define EXDEV 18 / Cross-device link /
#define ENODEV 19 / No such device /
#define ENOTDIR 20 / Not a directory /
#define EISDIR 21 / Is a directory /
#define EINVAL 22 / Invalid argument /
#define ENFILE 23 / File table overflow /
#define EMFILE 24 / Too many open files /
#define ENOTTY 25 / Not a typewriter /
#define ETXTBSY 26 / Text file busy /
#define EFBIG 27 / File too large /
#define ENOSPC 28 / No space left on device /
#define ESPIPE 29 / Illegal seek /
#define EROFS 30 / Read-only file system /
#define EMLINK 31 / Too many links /
#define EPIPE 32 / Broken pipe */
使用实例:
ret = request_irq(IRQ_NUM, irq_handler, IRQF_TRIGGER_FALLING, "MY_IRQ", dev);
if (ret)
printk(KERN_ERR "my_driver: Can't get assigned irq %d\n", IRQ_NUM);
free_irq函数用于释放request_irq函数申请的中断。它的原型为:
void free_irq(unsigned int irq, void *dev);
参数说明:
用法:
当我们不再需要使用某个中断时,必须调用free_irq函数释放它,否则该中断号将不会被重用,可能导致中断号资源短缺。
一个典型的示例如下:
ret = request_irq(IRQ_NUM, irq_handler, IRQF_TRIGGER_FALLING,
"MY_IRQ", dev);
// ......
// 不再需要该中断
free_irq(IRQ_NUM, dev);
需要注意的是,dev参数一定要和request_irq时传入的dev参数相同,否则free_irq会失败。
free_irq函数执行的主要操作是:
源码定义:
/include/linux/irqreturn.h
返回IRQ_HANDLED表示处理完了,返回IRQ_NONE在共享中断表示不处理
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
具体的3种返回状态含义如下:
irqreturn_t irq_handler(int irq, void *dev_id)
{
// ......处理中断
return IRQ_HANDLED;
}
这个处理程序成功处理了中断,所以返回IRQ_HANDLED。
如果我们的中断处理程序没有完全处理中断,而是触发了一个需要在线程环境下处理的工作,可以:
irqreturn_t irq_handler(int irq, void *dev_id)
{
// 触发处理工作
schedule_work(&some_work);
// 唤醒中断线程
return IRQ_WAKE_THREAD;
}
所以,irqreturn枚举类型给中断处理程序返回值定义了丰富的语义,我们可以根据实际情况进行灵活设置,实现更好的中断处理机制。
typedef irqreturn_t (*irq_handler_t)(int, void *);
函数用于申请中断,它定义在include/linux/interrupt.h头文件中。
原型为:
int devm_request_irq(struct device *dev,
unsigned int irq,
irq_handler_t handler,
unsigned long irqflags,
const char *devname,
void *dev_id);
参数说明:
返回值:
与request_irq的区别:
devm_request_irq将申请的中断"绑定"到dev指定的设备上。当该设备被驱动释放时,devm_request_irq也会自动释放中断。
而request_irq则需要我们手动调用free_irq来释放中断。
使用示例:
struct device *dev = ...; // 设备
ret = devm_request_irq(dev, irq, irq_handler, IRQF_TRIGGER_RISING,
"my_irq", dev);
if (ret)
printk(KERN_ERR "mydriver: Can't get assigned irq %d\n", irq);
// ......
退出驱动时,devm_request_irq自动释放中断
devm_request_irq简化了资源管理的工作,防止因为 programmer error 导致资源泄露的问题。所以在能使用devm_xxx系列函数的场景下,优先选择这些"managed"函数可以简化驱动开发并提高可靠性。
disable_irq, disable_irq_nosync和enable_irq都是定义在include/linux/interrupt.h头文件中的中断管理接口。
它们的原型分别为:
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
disable_irq和disable_irq_nosync的作用都是禁用指定的中断号irq。
区别在于:
// 禁用irq
disable_irq(irq);
// ......
// 重新使能
enable_irq(irq);
这三个函数对中断管理很有帮助,我们可以通过临时禁用和使能中断来:
/include/linux/irqflags.h
在linux 3.14及更高版本中,local_irq_save和local_irq_restore的定义为:
#define local_irq_save(flags) \
do { \
raw_local_irq_save(flags); \
trace_hardirqs_off(); \
} while (0)
#define local_irq_restore(flags) \
do { \
if (raw_irqs_disabled_flags(flags)) { \
raw_local_irq_restore(flags); \
trace_hardirqs_off(); \
} else { \
trace_hardirqs_on(); \
raw_local_irq_restore(flags); \
} \
} while (0)
它们的作用是:
unsigned long flags;
// 关闭中断,保存状态并跟踪
flags = local_irq_save();
// 临界区代码
do_some_noneatomic_work();
// 恢复中断状态并跟踪
local_irq_restore(flags);
作用范围是本CPU内。
这两个函数的定义实际上在include/linux/irqflags.h头文件中。
代码如您所示:
#define local_irq_enable() \
do { trace_hardirqs_on(); raw_local_irq_enable(); } while (0)
#define local_irq_disable() \
do { raw_local_irq_disable(); trace_hardirqs_off(); } while (0)
它们是两个宏定义,通过调用raw_local_irq_enable()和raw_local_irq_disable()这两个底层寄存器级函数来实现本地中断的启用和禁止。
在此过程中,它们还分别调用了trace_hardirqs_on()和trace_hardirqs_off()来进行debug跟踪。
所以,这两个函数是实现临界区保护与中断禁止的重要机制,它们通过操作CPU的状态来达到关闭与打开本地中断的效果,为需要原子操作的代码提供一个无中断的运行环境。
作用范围是本CPU内。
前面说了,底半部的实现机制有四种,分别是 tasklet 、 工作队列 、 软中断 、 线程化irq。接下来分别介绍:
tasklet的操作流程:
tasklet机制特点:
tasklet内核原理:
以上详细解释了tasklet机制的流程,特点,内核实现原理等方面内容。tasklet是一种轻量级的底半部机制,用于在中断处理的底半部执行可被抢占的较长时间工作。
tasklet是基于软中断实现的,运行于软中断上下文,仍然属于原子上下文的一种,在此不允许睡眠。
tasklet_struct是tasklet机制的核心数据结构,它的定义在include/linux/interrupt.h头文件中。
struct tasklet_struct {
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
该结构体包含了一个tasklet所需要的所有信息,具体的每个字段解释如下:
struct tasklet_struct *next;
next指针用于将所有tasklet连接成一个链表,便于内核管理与执行。
unsigned long state;
state字段表示tasklet的当前状态,可以是以下几种:
TASKLET_STATE_SCHED: 已触发,待执行
TASKLET_STATE_RUN: 当前正在执行handler函数
TASKLET_STATE_KILLED: 已杀死,handler函数不会再被调用
atomic_t count;
count是一个原子计数器,表示该tasklet当前的触发次数。内核会在handler函数执行前将其置0,以判断是否需要再次触发该tasklet。
void (*func)(unsigned long);
func指针指向该tasklet所需要执行的handler函数。当tasklet被调度执行时,内核会调用此函数来执行底半部逻辑。
unsigned long data;
data字段可用于传递参数给handler函数,它的值是在初始化tasklet时通过tasklet_init()传入的。
所以,tasklet_struct结构体中包含了tasklet所需要的一切信息:
在tasklet机制中,我们需要指定一个底半部处理函数(handler function)来完成实际的工作。
该函数的原型如下:
void handler_func(unsigned long data);
函数用于初始化一个tasklet。其定义在include/linux/interrupt.h头文件中。
原型为:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
参数解释:
举例:
#include
void tasklet_handler(unsigned long data)
{
// tasklet handler logic here
}
void init_tasklet(void)
{
struct tasklet_struct t;
tasklet_init(&t, tasklet_handler, 0);
}
这个例子初始化了一个tasklet t,并指定其handler函数为tasklet_handler。当这个tasklet被执行时,内核会调用tasklet_handler函数来完成底半部逻辑,并向其传递初始化时指定的数据0。
tasklet_init()函数在初始化tasklet时,会做以下工作:
DECLARE_TASKLET宏:
DECLARE_TASKLET(name, func, data)
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
DECLARE_TASKLET与 tasklet_init函数,两种方法的主要区别在于初始化的时机:
函数用于触发一个已经初始化的tasklet来执行。其定义在include/linux/interrupt.h头文件中。
原型为:
void tasklet_schedule(struct tasklet_struct *t);
参数t为指向要触发的tasklet_struct结构体的指针。
功能:该函数会标记传入的tasklet t为待执行状态, so在稍后恢复中断的合适时机,内核会调用我们初始化时为该tasklet指定的handler函数来执行底半部逻辑。
举例:
struct tasklet_struct t;
void tasklet_handler(unsigned long data)
{
printk(KERN_INFO "Tasklet running...\n");
}
void init_tasklet(void)
{
tasklet_init(&t, tasklet_handler, 0);
}
void trigger_tasklet(void)
{
tasklet_schedule(&t);
}
这里首先初始化了一个tasklet t,指定其handler函数为tasklet_handler。
然后在trigger_tasklet函数中,通过调用tasklet_schedule(&t)来触发这个tasklet。
此时,内核会标记这个tasklet为待执行状态。在稍后退出中断上下文的时候,内核会调用我们指定的tasklet_handler函数来执行底半部逻辑并打印信息。
所以,tasklet_schedule()函数的主要作用就是触发一个已经初始化好的tasklet,让内核在适当的时候执行其底半部handler函数。这是完成tasklet机制的关键一步。
函数用于手动执行一个tasklet的handler函数。其定义在kernel/softirq.c文件中。
原型为:
static void tasklet_action(struct softirq_action *a)
参数a为指向要执行的tasklet的softirq_action结构体的指针。该结构体包含了tasklet的所有信息,其中包含了我们初始化时指定的handler函数指针。
举例:
struct tasklet_struct t;
void tasklet_handler(unsigned long data)
{
printk(KERN_INFO "Tasklet running...\n");
}
void init_tasklet(void)
{
tasklet_init(&t, tasklet_handler, 0);
}
void trigger_tasklet(void)
{
tasklet_action((struct softirq_action *)&t);
}
这里我们首先初始化一个tasklet t,指定其handler函数为tasklet_handler。
然后在trigger_tasklet函数中,通过调用tasklet_action(&t)来手动执行这个tasklet的handler函数。
此时,内核会直接调用我们指定的tasklet_handler函数来执行底半部逻辑并打印信息。
作用:tasklet_action()函数的作用是手动触发一个tasklet的执行,让内核直接调用其handler函数完成工作,而不需要mark它为待执行状态并等待 later 执行。
这可用于一些特殊情况,我们需要手动立即执行一个tasklet的底半部逻辑,而不是触发其等待稍后执行。
所以,总结来说,tasklet_action()函数的作用是:
\qquad 工作队列的使用方法和tasket非常相似,但是工作队列的执行上下文是内核线程,因此可以调度和睡眠。
工作队列是运行在进程上下文,所以可以调度和睡眠
workqueue机制的详细说明如下:
work_struct是workqueue机制的核心数据结构,它定义在include/linux/workqueue.h头文件中。
原型如下:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
struct lockdep_map lockdep_map;
};
该结构体包含了一个work所需要的所有信息,具体的每个字段解释如下:
atomic_long_t data;
data是一个原子整型,用于传递参数给工作的handler函数。当我们初始化一个work时,可以指定此值,然后在handler函数中可以获取到该值。
struct list_head entry;
entry是一个链表头,内核会使用此链表将多个work连接起来,以形成工作队列的pending链表。通过此链表,内核可以高效地管理所有的已添加但未执行的work。
work_func_t func;
func是一个函数指针,指向该work的handler函数。当内核执行该work时,会调用此handler函数来完成实际的工作。
struct lockdep_map lockdep_map;
这个部分与锁有关,用于锁检测机制。我们这里不需要过多关注。
所以,总结来说,work_struct结构体中包含了work所需要的一切信息:
typedef void (*work_func_t)(struct work_struct *work);
INIT_WORK 宏用于初始化一个work结构体,为其指定handler函数。其定义在include/linux/workqueue.h头文件中。
语法为:
INIT_WORK(work, func);
struct work_struct my_work;
void work_handler(struct work_struct *work)
{
// handler function logic here
}
void init_work(void)
{
INIT_WORK(&my_work, work_handler);
}
这里我们定义了一个work_struct结构体my_work,并使用INIT_WORK宏来初始化它,指定其handler函数为work_handler。
INIT_WORK宏在初始化work时,会做以下工作:
函数用于将一个work添加到system_wq工作队列中。system_wq是内核的默认工作队列,用于大多数异步工作。
该函数的定义在kernel/workqueue.c文件中。
原码:
static inline bool schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}
参数work为指向要添加的work_struct结构体的指针。
该函数会将传入的work添加到system_wq工作队列的pending链表上,等待内核在适当的时候执行。
返回值:该函数会返回true if work was pending, false if it was not pending.
举例:
c
void work_handler(struct work_struct *work)
{
printk(“Work handler function called\n”);
}
struct work_struct my_work;
INIT_WORK(&my_work, work_handler);
void queue_work_example(void)
{
schedule_work(&my_work);
}
这里我们首先定义并初始化了一个work,指定其handler函数为work_handler。
然后在queue_work_example函数中,通过调用schedule_work(&my_work)来将这个work添加到system_wq工作队列中。
内核会在稍后选择一个时机,调用我们指定的work_handler函数来执行这个work,并打印相关信息。
所以,schedule_work()函数的主要作用就是将一个work提交到system_wq工作队列中,等待内核在适当的时候执行该work的handler函数。
这是使用workqueue机制的关键步骤之一,我们通过调用该函数来异步调度工作,并由内核运行其处理逻辑。
和其他正在执行的work比,通过该函数添加的work可能立即执行,也可能稍后执行,这取决于内核的调度。
flush_workqueue()函数用于等待一个工作队列中的所有工作完成执行。其定义在kernel/workqueue.c文件中。
原型为:
void flush_workqueue(struct workqueue_struct *wq);
参数wq为要等待的工作队列。如果传入NULL,则会等待system_wq默认工作队列。
该函数会阻塞当前执行环境,直到指定工作队列wq中的所有work完成执行为止。
举例:
void work_handler(struct work_struct *work)
{
printk("Work handler function called\n");
}
struct work_struct work1, work2;
INIT_WORK(&work1, work_handler);
INIT_WORK(&work2, work_handler);
void test_flush_workqueue(void)
{
schedule_work(&work1);
schedule_work(&work2);
flush_workqueue(NULL);
printk("All works completed!\n");
}
这里我们定义并初始化了两个work,都指定work_handler为handler函数。
然后分别通过schedule_work()将这两个work添加到system_wq工作队列。
紧接着,我们调用flush_workqueue(NULL)来等待system_wq中的所有work完成。
该函数会阻塞,直到work1和work2都完成执行,然后继续往下执行,打印相关信息。
所以,flush_workqueue()函数的主要作用是:
函数用于取消一个work,并等待其处理逻辑完成。其定义在kernel/workqueue.c文件中。
原型为:
bool cancel_work_sync(struct work_struct *work);
参数work为要取消的work。
该函数会将传入的work从其所在的工作队列的 pending 链表中删除,以取消该work;
并会等待该work目前正在执行的任务完成,才会返回。
返回值:如果成功取消work并等待其完成,则返回true;如果work已经完成执行则返回false。
举例:
void work_handler(struct work_struct *work)
{
printk("Work handler function called\n");
}
struct work_struct my_work;
INIT_WORK(&my_work, work_handler);
void test_cancel_work_sync(void)
{
schedule_work(&my_work);
if (cancel_work_sync(&my_work))
printk("Work cancelled!\n");
else
printk("Work already finished!\n");
}
这里我们定义并初始化一个work,指定其handler函数为work_handler。
然后通过schedule_work()将该work添加到默认工作队列执行。
紧接着,我们调用cancel_work_sync(&my_work)试图取消该work。
如果work尚未开始执行,则会从工作队列中删除并取消该work,并打印相关信息;
如果work已经完成执行,则函数会直接返回false,并打印相应信息。
所以,cancel_work_sync()函数的主要作用是:
\qquad 软中断(Softirq)也是一种传统的底半部处理机制,它的执行时机通常是顶半部返回的时候。
tasklet和软中断都是基于软中断实现的,运行于软中断上下文,仍然属于原子上下文的一种,在此不允许睡眠。
softirq机制的详细说明如下:
/include/linux/interrupt.h
struct softirq_action
{
void (*action)(struct softirq_action *);
};
softirq_action结构体用于定义一个软中断的handler函数及相关信息。其定义在include/linux/interrupt.h头文件中。
action是一个函数指针,指向该软中断的handler函数。当该软中断被触发并执行时,内核会调用此handler函数来完成实际的工作。
handler函数的原型为:
void handler_func(struct softirq_action *a)
参数a为指向该软中断对应的softirq_action结构体的指针。在handler函数中可以使用该指针访问软中断的信息。
举例:
void softirq_handler(struct softirq_action *a)
{
printk("Softirq handler function called\n");
}
void test_softirq(void)
{
struct softirq_action a;
a.action = softirq_handler;
open_softirq(3, &a);
}
这里我们定义了一个softirq_action结构体a,并指定其handler函数为softirq_handler。
然后通过open_softirq()来开启软中断号3,注册该handler函数。
当软中断3被触发时,内核会调用我们指定的softirq_handler函数来执行工作,并打印信息。
所以,softirq_action结构体为每个软中断保存了其handler函数等重要信息。内核通过管理这些结构体,来实现软中断机制并在适当的时候调用相应的handler函数。
这个结构体是软中断机制的基础,它抽象出了软中断的handler实体,用于在内核的不同地方注册和触发软中断。
open_softirq()函数用于为指定的软中断号注册一个handler函数。声明在/include/linux/interrupt.h头文件里,
其定义在kernel/softirq.c文件中。
原型为:
void open_softirq(int nr, void (*action)(struct softirq_action *))
void softirq_handler(struct softirq_action *a)
{
printk("Softirq handler function called\n");
}
void test_open_softirq(void)
{
open_softirq(3, softirq_handler);
}
这里我们调用open_softirq()来开启软中断号3,并注册处理函数softirq_handler。
当软中断3被触发时,内核会调用我们指定的softirq_handler函数,并打印相关信息。
所以,open_softirq()函数的主要作用是:
raise_softirq()函数用于触发一个软中断。其定义在kernel/softirq.c文件中。
原型为:
void raise_softirq(unsigned int nr)
参数nr为要触发的软中断号。
该函数会标记指定软中断号nr的softirq待处理位图,表示该软中断现在有一个待处理事件。
在适当的时候,内核会检查每个CPU的软中断位图,并执行该软中断对应的handler函数。
举例:
void softirq_handler(struct softirq_action *a)
{
printk("Softirq handler function called\n");
}
void test_raise_softirq(void)
{
open_softirq(3, softirq_handler);
raise_softirq(3);
}
这里我们首先通过open_softirq()为软中断号3注册handler函数softirq_handler。
然后调用raise_softirq(3)来触发软中断号3。
内核会在稍后适当的时候执行该软中断,调用我们指定的softirq_handler函数,并打印相关信息。
所以,raise_softirq()函数的主要作用是:
local_bh_disable()和local_bh_enable()函数用于禁止和重新开启本CPU上的软中断及tasklet的底半部处理。
这两个函数的定义在include/linux/hardirq.h头文件中。
原型为:
void local_bh_disable(void);
void local_bh_enable(void);
local_bh_disable()函数会禁止本CPU上的软中断处理、时钟事件处理和BSD会和处理。
local_bh_enable()函数重新开启上述被禁止的底半部处理。
这两个函数用于在本地CPU上临时禁止底半部载入,以避免并发执行导致的问题。在两者之间的代码段,只有硬中断会被执行。
举例:
void bh_test(void)
{
local_bh_disable();
// Critical section, only hard IRQ handlers will run
...
local_bh_enable();
}
这里我们通过local_bh_disable()禁止本CPU的底半部处理,在中间的代码区域执行一些关键工作。
然后调用local_bh_enable()再重新开启底半部处理。
这确保了中间代码区域只会被硬中断抢占,避免软中断或其他底半部事件导致的并发问题。
所以,这两个函数的主要作用是:
这个数组定义在kernel/softirq.c文件中,包含每个软中断号对应的softirq_action结构体。
softirq_vec数组的定义如下:
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
每个元素softirq_action包含了一个软中断号的handler函数和其他信息。
我们可以通过直接打印这个数组中的信息来查看内核已注册的软中断详情。
举例:
void test_print_softirqs(void)
{
int i;
printk("Registered softirqs: \n");
for (i = 0; i < NR_SOFTIRQS; i++) {
if (softirq_vec[i].action)
printk("softirq %d, handler function %ps\n", i, softirq_vec[i].action);
}
}
这里我们循环遍历softirq_vec数组中的每个元素,并打印非空元素的软中断号和handler函数地址。
这会输出内核注册表中的所有软中断信息,包括号码和处理函数。
在我的环境中,执行该函数会打印出类似如下信息:
Registered softirqs:
softirq 0, handler function events_handle_irq
softirq 1, handler function work_handle_irq
所以,如果您想了解内核当前已注册的软中断信息,可以直接打印softirq_vec数组中的内容,它包含了每个软中断的处理函数等详细信息。
使用软中断机制的一般步骤是:
定义软中断号 -> 注册处理函数 -> 触发软中断 -> 软中断处理函数完成工作 -> (可选)重新触发软中断循环处理
这是一个简单但非常有用的机制,用于异步下半部处理和循环工作。
软中断机制的一般模板如下:
#define MY_SOFTIRQ 10
void my_softirq_handler(struct softirq_action *a)
{
...
}
static int __init example_init(void)
{
open_softirq(MY_SOFTIRQ, my_softirq_handler);
}
void trigger_my_softirq(void)
{
raise_softirq(MY_SOFTIRQ);
}
void my_softirq_handler(struct softirq_action *a)
{
printk("Running my softirq handler \n");
...
// Handle bottom half work
}
void my_softirq_handler(struct softirq_action *a)
{
...
// Handle some work
raise_softirq(MY_SOFTIRQ); // Re-trigger
}
中断底半部线程化(IRQ threading)是Linux内核中一个重要的机制。它用于将硬件中断的底半部处理工作转移到内核线程上执行,从而实现IRQ的线程化。
该机制的主要工作流程如下:
IRQ线程化的主要特点是:
要实现IRQ的线程化,主要涉及以下方面:
函数用于请求一个带有线程处理的中断。它定义在
该函数的作用是:注册一个中断的线程处理函数和中断处理函数,并将中断的底半部处理工作转移到线程上执行,实现中断的线程化。
其函数原型为:
int __must_check
request_threaded_irq(unsigned int irq,
irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags,
const char *name,
void *dev)
各个参数的含义如下:
该函数的主要工作是:
举例:
irq_handler_t thread_fn(void *dev)
{
... // 中断线程函数,完成底半部工作
}
irq_handler_t irq_handler(int irq, void *dev)
{
... // 中断处理函数,完成顶半部工作
}
int err;
err = request_threaded_irq(IRQ_NUM, irq_handler, thread_fn,
IRQF_SHARED, "example", dev);
if (err)
panic("IRQ registration failed");
这里我们请求IRQ_NUM这个中断号,注册irq_handler为顶半部处理函数,thread_fn为底半部线程函数。
该中断被配置为可共享,并给出了一个示例名称用于调试。
request_threaded_irq()函数是实现中断线程化的关键接口,理解其实现机制与参数含义,对学习Linux内核中断处理机制大有裨益。
是针对资源管理的版本,用于请求一个线程化的中断。函数是针对资源管理的版本,用于请求一个线程化的中断。
它的函数原型为:
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_threaded_irq()函数相比,devm_request_threaded_irq()有以下不同:
除此之外,其余参数与功能与request_threaded_irq()基本相同,用于请求一个线程化的中断处理。
devm_request_threaded_irq()函数的工作流程如下:
所以,与request_threaded_irq()相比,devm_request_threaded_irq()带来的主要好处是:
int err;
err = devm_request_threaded_irq(&pdev->dev, irq, irq_handler,
thread_fn, IRQF_SHARED,
"example", dev);
if (err)
panic("devm irq request failed");
这里我们为pdev设备请求一个IRQ中断,并注册irq_handler和thread_fn为其处理函数,配置其为可共享中断。
一旦pdev设备被移除,对应中断资源会被自动释放。
所以,对有设备资源管理需求的驱动来说,devm_request_threaded_irq()通常是一个更好的选择。理解其实现机制,对编写规范的驱动程序很有帮助。
中断底半部线程化irq机制的一般模板如下:
1、定义中断号:
在中断编号头文件(如arch/x86/include/asm/irq_vectors.h)中定义一个中断号,如:
#define IRQ_NUM 16
2、为中断定义线程函数:
定义一个内核线程,作为中断底半部处理函数,如:
irq_handler_t irq_thread_fn(void *data)
{
...
}
3、 注册中断处理函数与线程函数:
在驱动初始化时调用request_threaded_irq()注册中断处理函数与线程函数,如:
int err;
err = request_threaded_irq(IRQ_NUM, irq_handler, irq_thread_fn,
IRQF_SHARED, "example", dev);
4、 中断处理函数:
中断处理函数仅完成必要的顶半部工作,并标记底半部工作待处理,如:
irq_handler_t irq_handler(int irq, void *data)
{
... // 顶半部处理
mark_irq_threaded(); // 标记底半部待处理
}
5、 中断线程函数:
中断线程函数在其它线程环境下运行,并完成底半部工作,如:
irq_handler_t irq_thread_fn(void *data)
{
... // 底半部处理逻辑
}
6、 重新检查待处理中断(可选):
中断线程函数在处理完当前中断后,可以通过irq_bit在butmp map中重新检查是否有其他待处理中断,如:
irq_handler_t irq_thread_fn(void *data)
{
... // 处理当前中断底半部工作
irq_thread_check_affinity(); // 检查其他待处理中断
}
7、 释放中断(可选):
如果在驱动卸载时需要释放中断,则调用free_irq()完成释放,如:
free_irq(IRQ_NUM, dev);
所以, thread irq机制的使用模板是:
定义中断号 -> 注册处理函数与线程函数 -> 中断处理函数标记底半部待处理
-> 中断线程函数完成底半部工作
-> (可选)重新检查其他待处理中断
-> (可选)在卸载时调用free_irq()释放中断
理解此模板,是深入学习Linux内核中断底半部分离与线程化机制的基础。
在这个设备树相关操作的头文件/include/linux/of_irq.h
irq_of_parse_and_map函数用于从设备树中获取中断号。它定义在include/linux/of_irq.h头文件中。
int irq_of_parse_and_map(struct device_node *node, int index);
参数说明:
返回值:
struct device_node *node = dev->of_node; // 获取设备节点
int irq = irq_of_parse_and_map(node, 0); // 获取第一个中断
if (irq > 0)
ret = request_irq(irq, xxx_interrupt, IRQF_TRIGGER_RISING,
"xxx_irq", xxx_dev); // 请求该中断
else
printk("xxx irq_of_parse_and_map failed!");
这个例子从设备节点中获取第一个中断,即索引为0的中断,并向内核请求该中断,连接中断处理程序xxx_interrupt。
irq_of_parse_and_map函数简化了从设备树中获取中断号的过程。
\qquad 将开发板上的一个按键,设定成下降延触发的方式。每按一次后,按键的数据存主内核的缓冲内。应用层有个程序阻塞式读取缓冲,并打印出相应的数据。
华清fs4412(exynos4412)开发板,linux3.14
\qquad 板上有三个按键,分别为K2、K3、K4。本实验只用到K2,K2按键连接的是GPX1_1这个GPIO接口,其对应的外部中断名称为XEINT9。GPX1_1是通过R13电阻上拉为高电平,当按下K2后,接地导通,则GPX1_1变为低电平。
查询中断表:
该表在exynos4412的用户手册中:
该节点定义在设备树文件在arch/arm/boot/dts/exynos4.dtsi中,系统已帮我们写好,需要知道其含义,以及该含义的出处
该节点定义在设备树 文件在arch/arm/boot/dts/exynos4x12-pinctrl.dtsi ,系统已帮我们写好,需要知道其含义及出处
interrupts说明符的含义及格式的说明文档在/Documentation/devicetree/bindings/arm/gic.txt
#interrupt-cells : Specifies the number of cells needed to encode an interrupt source.
The type shall be a < u32 > and the value shall be 3.
- The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
interrupts.- The 2nd cell contains the interrupt number for the interrupt type.
SPI interrupts are in the range [0-987]. PPI interrupts are in the range [0-15].- The 3rd cell is the flags, encoded as follows:
- bits[3:0] trigger type and level flags.
1 = low-to-high edge triggered
2 = high-to-low edge triggered
4 = active high level-sensitive
8 = active low level-sensitive
- bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of the 8 possible cpus attached to the GIC. A bit set to ‘1’ indicated the interrupt is wired to that CPU. Only valid for PPI interrupts.
该节点的树文件为arch/arm/boot/dts/exynos4412-fs4412.dts
中断产生者的interrupts说明符格式含义:/Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
外部GPIO中断:为了支持外部GPIO中断,应在引脚控制器设备节点中指定以下属性。
- interrupt-parent:将外部GPIO中断转发到的中断父级的标题。
- interrupts控制器的中断说明符。中断说明符的格式和值取决于控制器的中断父级。
此外,支持GPIO中断的每个引脚组的节点中必须存在以下属性:
- interrupt-controller:将控制器节点标识为中断父节点。
- #interrupt-cells:此属性的值应为2。
- First Cell: represents the external gpio interrupt number local to the external gpio interrupt space of the controller.
表示控制器本地gpio中断编号(列表的顺序号,从0开始计算)。- Second Cell:标识中断类型的标志
- 1=上升沿触发
- 2=触发下降沿
- 3=上升沿和下降沿触发
- 4=高电平触发
- 8=低电平触发
以上内容,从上到下,把设备树的层次结构,以及cell的含义的规则出处都讲清楚了。因为在开发驱动时,从硬件到设备树到程序里的每一个配置对应关系必须清楚
/*************************************************************************
> File Name: publuc.h
************************************************************************/
#ifndef _PUBLUC_H
#define _PUBLUC_H
enum KEYNUM {
KEY2 =2,
KEY3,
KEY4,
};
enum KEYSTATUE{
KEY_DOWN = 0,
KEY_UP = 1,
};
struct key_data_t {
int key_num;
int statue;
int new;
};
#endif
/*************************************************************************
> File Name:key-mem.c
************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include “public.h”
/1、定义重要的变量及结构体/
#define DEV_NUM 1
struct x_dev_t{
struct cdev my_dev; //cdev设备描述结构体变量
wait_queue_head_t rq; //读等待队列
spinlock_t lock;
unsigned int gpio_num;
struct key_data_t key_data;
int IRQ;
};
struct x_dev_t *x_dev;
/所有驱动函数声明/
ssize_t read (struct file *, char __user *, size_t, loff_t *);
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
//驱动操作函数结构体,成员函数为需要实现的设备操作函数指针
//简单版的模版里,只写了open与release两个操作函数。
struct file_operations fops={
.open = open,
.release = release,
.read = read,
};
//中断处理函数
irqreturn_t irq_handler(int irq,void *p){
struct x_dev_t *xdev =(struct x_dev_t *)p;
spin_lock(&xdev->lock);
xdev-> key_data.key_num = irq;
xdev-> key_data.statue = gpio_get_value(xdev->gpio_num);
xdev->key_data.new = 1;
printk("driver: Interrupt is happend , IRQ = %d\n" , xdev->key_data.key_num);
printk("driver: key status is %d\n",xdev->key_data.statue);
spin_unlock(&xdev->lock);
return IRQ_HANDLED;
}
/3、初始化 cdev结构体,并将cdev结构体与file_operations结构体关联起来/
/这样在内核中就有了设备描述的结构体cdev,以及设备操作函数的调用集合file_operations结构体/
static int cdev_setup(struct x_dev_t *p_dev , dev_t devno ){
int unsucc =0;
int ret = 0;
struct device_node *IRQ_NODE=NULL,*P=NULL;
cdev_init(&p_dev->my_dev , &fops);
p_dev->my_dev.owner = THIS_MODULE;
/*4、注册cdev结构体到内核链表中*/
unsucc = cdev_add(&p_dev->my_dev , devno , 1);
if (unsucc){
printk("driver : cdev add faild \n");
return -1;
}
spin_lock_init( &p_dev->lock); //初始化自旋锁,为1
init_waitqueue_head(&p_dev->rq);//22初始化读等待队列
//读取设备树key节点 和gpio属性
IRQ_NODE = of_find_node_by_path("/key2_node");
p_dev->gpio_num = of_get_named_gpio(IRQ_NODE , "key2_gpio",0);
if (IRQ_NODE){
p_dev->IRQ = irq_of_parse_and_map(IRQ_NODE ,0);
if (p_dev->IRQ == 0){
printk("driver:IRQ is not found , MINOR is %d\n ",MINOR(devno));
return -1;
}
ret = request_irq(p_dev->IRQ , irq_handler,IRQF_TRIGGER_FALLING , "fs4412_key2-4",p_dev);
if (ret){
return -1;
}
}else{
printk("driver : IRQ_NODE is not found,MINOR is %d\n", MINOR(devno));
return -1;
}
return 0;
}
static int __init my_init(void){
int major , minor;
dev_t devno;
int unsucc =0;
int i=0;
x_dev = kzalloc(sizeof(struct x_dev_t)*DEV_NUM , GFP_KERNEL);
if (!x_dev){
printk(" driver : allocating memory is failed");
return -1;
}
/*2、创建 devno */
unsucc = alloc_chrdev_region(&devno , 0 , DEV_NUM , "key_irq");
if (unsucc){
printk(" driver : creating devno is failed\n");
return -1;
}else{
major = MAJOR(devno);
minor = MINOR(devno);
printk("diver : major = %d ; minor = %d\n",major,minor);
}
/*3、 初始化cdev结构体,并联cdev结构体与file_operations.*/
/*4、注册cdev结构体到内核链表中*/
for (i=0;i
}
static void __exit my_exit(void) } /5、驱动函数的实现/ int open(struct inode *pnode , struct file *pf){ } /file_operations结构全成员函数.read的具体实现/ copy: } module_init(my_init); insmod key-irq.ko 然后,通过按板上的key2 或 key3按键,就会在相应的信息在屏上出现。
{
int i=0;
dev_t devno;
devno = x_dev->my_dev.dev;
for (i=0 ; i cdev_del(&(x_dev+i)->my_dev);
free_irq((x_dev+i)->IRQ , (x_dev+i));
}
unregister_chrdev_region(devno , DEV_NUM);
kfree(x_dev);
printk("***************the driver operate_memory exit************\n");
/file_operations结构全成员函数.open的具体实现/
int minor = MINOR(pnode->i_rdev);
int major = MAJOR(pnode->i_rdev);
struct x_dev_t *p = container_of(pnode->i_cdev , struct x_dev_t , my_dev);
pf->private_data = p; //把全局变量指针放入到struct file结构体里if (pf->f_flags & O_NONBLOCK){ //非阻塞
printk("driver : block_memory[%d , %d] is opened by nonblock mode\n",major , minor);
}else{
printk("driver : block_memory[%d , %d] is opened by block mode\n",major,minor);
}
return 0;
/file_operations结构全成员函数.release的具体实现/
int release(struct inode *pnode , struct file *pf){
printk(“block_memory is closed \n”);
return 0;
}
ssize_t read (struct file * pf, char __user * buf, size_t size , loff_t * ppos){
int res;
struct x_dev_t *pdev = pf->private_data;
if (pf->f_flags & O_NONBLOCK ){ // nonblock
if (pdev->key_data.new == 0){
printk(“driver:no new interrupt\n”);
return 0;
}
goto copy;
}wait_event_interruptible(pdev->rq,(pdev->key_data.new >0));
spin_lock(&pdev->lock);
res = copy_to_user(buf , &pdev->key_data, sizeof(struct key_data_t));
spin_unlock(&pdev->lock);if (res == 0)
return sizeof(struct key_data_t);
else
return 0;
module_exit(my_exit);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“”);5.5.3 测试
mknod /dev/key-irq c 251 0
chmod 777 /dev/key-irq